funifier-mcp 0.3.20 → 0.3.21

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.
@@ -0,0 +1,718 @@
1
+ # Server-side Utils — Referência
2
+
3
+ ## 1. Visão Geral
4
+
5
+ ### 1.1 O que é este documento
6
+
7
+ Este documento descreve as classes utilitárias do pacote `com.funifier.engine.util` do `funifier-service` disponíveis em código server-side — triggers, schedulers e public endpoints. São classes **puramente estáticas**: todos os métodos são chamados pelo nome totalmente qualificado (ou nome curto, quando pré-importado pelo runtime) sem instanciação.
8
+
9
+ ### 1.2 Como referenciar no código
10
+
11
+ Em triggers e schedulers, as classes do pacote `com.funifier.engine.util` são pré-importadas pelo runner. Use o **nome curto** diretamente:
12
+
13
+ ```groovy
14
+ void trigger(event, entity, player, database) {
15
+ String json = JsonUtil.toJson(entity) // OK — nome curto
16
+ boolean ok = CpfUtil.isValid(entity.cpf) // OK — nome curto
17
+ }
18
+ ```
19
+
20
+ Se por algum motivo um nome curto não funcionar (Public Endpoint tem sandbox mais restrito), use o FQN:
21
+
22
+ ```groovy
23
+ String json = com.funifier.engine.util.JsonUtil.toJson(entity)
24
+ ```
25
+
26
+ > ⚠️ No **Public Endpoint**, o sandbox bloqueia `BCrypt` e `HttpUtil` com nome qualificado `com.*`. Use somente `JsonUtil` e `StringUtil` por nome curto nesse contexto. Para os demais, prefira Trigger ou Scheduler. Consulte `guides/java-libraries.md` §1.5 para a tabela completa do sandbox.
27
+
28
+ ### 1.3 Índice de decisão
29
+
30
+ | Necessidade | Classe | Seção |
31
+ |---|---|---|
32
+ | Serializar/parsear JSON | `JsonUtil` | §2 |
33
+ | Verificar string nula/vazia/em branco | `StringUtil` | §3 |
34
+ | Validar CPF (formato String ou número) | `CpfUtil` | §4 |
35
+ | Formatar número como moeda | `FormatterUtil` | §5 |
36
+ | Parsear inteiros/doubles com segurança | `ParserUtil` | §6 |
37
+ | Avaliar expressão inline `{{= ... }}` | `ExpressionUtils` | §7 |
38
+ | Renderizar template Mustache `{{var}}` | `MustacheUtils` | §8 |
39
+ | Comparar objetos e gerar changelog | `DiffUtil` | §9 |
40
+ | Criptografar campo com chave simétrica (AES) | `AesCrypt` | §10 |
41
+ | Hash/verificar senha (Blowfish) | `BCrypt` | §11 |
42
+ | Criptografar bloco de dados com PGP/RSA | `PgpCrypt` | §12 |
43
+ | Gerar QR code para TOTP / 2FA | `OtpAuthUtil` | §13 |
44
+ | Fazer requisição HTTP externa (GET/POST) | `HttpUtil` | §14 |
45
+ | Ler URL como string / limpar parâmetros Funifier | `UrlUtil` | §15 |
46
+ | Gerar arquivo Excel (XLSX) a partir de lista | `ExcelUtil` | §16 |
47
+ | Paginar resultado de aggregate Jongo | `PaginationUtil` | §17 |
48
+ | Converter InputStream em arquivo temporário | `FileUtil` | §18 |
49
+ | Datas e cálculo de períodos | `DateUtil` | §19 |
50
+ | Internos do motor (não usar diretamente) | — | §20 |
51
+
52
+ ---
53
+
54
+ ## 2. JsonUtil
55
+
56
+ **FQN:** `com.funifier.engine.util.JsonUtil`
57
+
58
+ Serialização e desserialização JSON baseada em Jackson. Pré-importada pelo runner — disponível em todos os runtimes incluindo Public Endpoint.
59
+
60
+ > `guides/java-libraries.md` §2 cobre os métodos básicos com mais exemplos contextuais (triggers, Public Endpoint). Este documento consolida a API completa.
61
+
62
+ ### Métodos públicos
63
+
64
+ | Método | Retorno | Descrição |
65
+ |--------|---------|-----------|
66
+ | `toJson(Object obj)` | `String` | Objeto → JSON indentado; campos null incluídos |
67
+ | `toJsonRemoveNullFields(Object obj)` | `String` | Objeto → JSON sem campos null |
68
+ | `toBsonStrictMode(Object obj)` | `String` | Objeto → BSON strict mode (null map values incluídos) |
69
+ | `toBsonStrictModeList(List<Object> list)` | `String` | Lista → BSON strict mode |
70
+ | `fromJson(String json, Class<T> valueType)` | `T` | JSON → objeto tipado |
71
+ | `fromJson(String json, Class<T> valueType, boolean showError)` | `T` | JSON → objeto tipado; controla log de erros |
72
+ | `fromJson(String json)` | `DBObject` | JSON → `DBObject` MongoDB |
73
+ | `fromJsonToMap(String json)` | `Map<String, Object>` | JSON → Map |
74
+ | `fromFunifierBson(String json)` | `Object` | Parseia BSON Funifier (com suporte a `$date`, `$oid`) |
75
+ | `fromFunifierBsonWithParams(String json, HashMap<String, Object> params)` | `Object` | Parseia BSON Funifier substituindo `$param` |
76
+
77
+ ### Exemplos
78
+
79
+ ```groovy
80
+ // Serializar entity para string
81
+ String json = JsonUtil.toJson(entity)
82
+
83
+ // Parsear JSON recebido
84
+ Map<String, Object> data = JsonUtil.fromJsonToMap('{"name":"João","age":30}')
85
+
86
+ // Converter para tipo específico
87
+ Player p = JsonUtil.fromJson(jsonStr, Player.class)
88
+
89
+ // Sem campos null (útil para salvar no banco)
90
+ String compact = JsonUtil.toJsonRemoveNullFields(entity)
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 3. StringUtil
96
+
97
+ **FQN:** `com.funifier.engine.util.StringUtil`
98
+
99
+ Verificações simples de string nula/vazia/em branco. Útil como guarda antes de processar campos textuais.
100
+
101
+ ### Métodos públicos
102
+
103
+ | Método | Retorno | Descrição |
104
+ |--------|---------|-----------|
105
+ | `isEmpty(String s)` | `boolean` | `true` se `null` ou `length < 1` |
106
+ | `isBlank(String s)` | `boolean` | `true` se `null`, vazio ou apenas espaços |
107
+
108
+ ### Exemplos
109
+
110
+ ```groovy
111
+ void trigger(event, entity, player, database) {
112
+ if (StringUtil.isBlank(entity.email)) {
113
+ throw new RuntimeException("E-mail obrigatório")
114
+ }
115
+ }
116
+ ```
117
+
118
+ ---
119
+
120
+ ## 4. CpfUtil
121
+
122
+ **FQN:** `com.funifier.engine.util.CpfUtil`
123
+
124
+ Validação e conversão de CPF brasileiro. Aceita string formatada (`"123.456.789-09"`), string pura (`"12345678909"`) ou número (`Long`).
125
+
126
+ > ⚠️ **CNPJ:** o método `isValidCNPJ` está comentado na fonte atual — **não disponível**. Use validação própria para CNPJ.
127
+
128
+ ### Métodos públicos
129
+
130
+ | Método | Retorno | Descrição |
131
+ |--------|---------|-----------|
132
+ | `isValid(Object cpf)` | `boolean` | Valida CPF (`String` ou `Number`); `false` se null |
133
+ | `toLong(Object cpf)` | `long` | Converte CPF para `long` (remove `.` e `-`); `0L` se null |
134
+
135
+ ### Exemplos
136
+
137
+ ```groovy
138
+ void trigger(event, entity, player, database) {
139
+ def cpf = entity.cpf
140
+ if (!CpfUtil.isValid(cpf)) {
141
+ throw new RuntimeException("CPF inválido: " + cpf)
142
+ }
143
+ long cpfNumerico = CpfUtil.toLong(cpf) // 12345678909L
144
+ entity.cpfLong = cpfNumerico
145
+ }
146
+ ```
147
+
148
+ ```groovy
149
+ // Funciona com string formatada
150
+ CpfUtil.isValid("695.494.861-68") // true
151
+ CpfUtil.toLong("695.494.861-68") // 69549486168L
152
+
153
+ // Funciona com long
154
+ CpfUtil.isValid(69549486168L) // true
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 5. FormatterUtil
160
+
161
+ **FQN:** `com.funifier.engine.util.FormatterUtil`
162
+
163
+ Formatação de número como moeda com suporte a locale e padrão de formato.
164
+
165
+ ### Métodos públicos
166
+
167
+ | Método | Retorno | Descrição |
168
+ |--------|---------|-----------|
169
+ | `numberCurrency(Object input, String currency, String locale, String format)` | `String` | Formata `input` como moeda |
170
+
171
+ - **`currency`**: código ISO 4217 — ex. `"BRL"`, `"USD"`, `"EUR"`
172
+ - **`locale`**: código de país — ex. `"BR"`, `"US"`, `"DE"`
173
+ - **`format`**: padrão `DecimalFormat` — ex. `"#,##0.00"` (ou `""` para usar o padrão da moeda/locale)
174
+
175
+ ### Exemplos
176
+
177
+ ```groovy
178
+ // R$ 1.234,56
179
+ String valor = FormatterUtil.numberCurrency(1234.56, "BRL", "BR", "#,##0.00")
180
+
181
+ // $1,234.56
182
+ String usd = FormatterUtil.numberCurrency(1234.56, "USD", "US", "#,##0.00")
183
+
184
+ // Usando padrão padrão da moeda
185
+ String padrao = FormatterUtil.numberCurrency(entity.preco, "BRL", "BR", "")
186
+ ```
187
+
188
+ ---
189
+
190
+ ## 6. ParserUtil
191
+
192
+ **FQN:** `com.funifier.engine.util.ParserUtil`
193
+
194
+ Conversão e arredondamento de números com proteção contra exceções.
195
+
196
+ ### Métodos públicos
197
+
198
+ | Método | Retorno | Descrição |
199
+ |--------|---------|-----------|
200
+ | `parseInt(String i)` | `int` | Converte string para int; retorna `0` se inválido |
201
+ | `formatDouble(double precoDouble, String format)` | `Double` | Formata double com padrão `DecimalFormat`; retorna `null` se erro |
202
+ | `setDoubleDecimalScale(double value, int scale)` | `double` | Arredonda para N casas decimais (HALF_UP); retorna original se erro |
203
+
204
+ ### Exemplos
205
+
206
+ ```groovy
207
+ int qty = ParserUtil.parseInt(entity.quantity) // "3" → 3; "abc" → 0
208
+
209
+ double preco = ParserUtil.setDoubleDecimalScale(10.56789, 2) // → 10.57
210
+
211
+ Double formatted = ParserUtil.formatDouble(1234.5678, "#,##0.00") // → 1234.57
212
+ ```
213
+
214
+ ---
215
+
216
+ ## 7. ExpressionUtils
217
+
218
+ **FQN:** `com.funifier.engine.util.ExpressionUtils`
219
+
220
+ Avalia blocos de expressão `{{= expr }}` em templates, com sandbox Groovy. Os blocos `{{var}}` comuns (Mustache) são deixados intactos. Útil para mensagens dinâmicas com cálculos.
221
+
222
+ ### Métodos públicos
223
+
224
+ | Método | Retorno | Descrição |
225
+ |--------|---------|-----------|
226
+ | `evaluateInline(String template, Map<String, Object> params, List<String> exceptions)` | `String` | Substitui cada `{{= expr }}` pelo resultado; erros vão para `exceptions` |
227
+
228
+ **Funções disponíveis dentro das expressões:**
229
+
230
+ | Função | Comportamento |
231
+ |--------|--------------|
232
+ | `round(x)` | Arredonda para inteiro mais próximo |
233
+ | `round(x, places)` | Arredonda para N casas (HALF_UP) |
234
+ | `ceil(x)` | Teto (inteiro) |
235
+ | `floor(x)` | Piso (inteiro) |
236
+ | `abs(x)` | Valor absoluto |
237
+ | `add(a, b)` | Soma |
238
+ | `subtract(a, b)` | Subtração |
239
+ | `dateFormat(d, pattern)` | Formata Date com `SimpleDateFormat` |
240
+
241
+ > ⚠️ O sandbox não permite `import`, acesso a `System`, `File`, etc. Apenas aritmética, comparação e as funções acima.
242
+
243
+ ### Exemplos
244
+
245
+ ```groovy
246
+ Map params = [progress: [percent_completed: 75.6, time: [total: 10, spent: 6]]]
247
+ List errors = []
248
+
249
+ String msg = ExpressionUtils.evaluateInline(
250
+ "Você completou {{= round(progress.percent_completed) }}% — faltam {{= progress.time.total - progress.time.spent }} dias",
251
+ params,
252
+ errors
253
+ )
254
+ // → "Você completou 76% — faltam 4 dias"
255
+
256
+ if (!errors.isEmpty()) {
257
+ println("Erros: " + errors)
258
+ }
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 8. MustacheUtils
264
+
265
+ **FQN:** `com.funifier.engine.util.MustacheUtils`
266
+
267
+ Renderização de templates Mustache (`{{variavel}}`). Para expressões matemáticas use `ExpressionUtils` (§7). Ambos podem ser combinados: `ExpressionUtils` processa `{{= ... }}` primeiro, depois `MustacheUtils` resolve `{{var}}`.
268
+
269
+ ### Métodos públicos
270
+
271
+ | Método | Retorno | Descrição |
272
+ |--------|---------|-----------|
273
+ | `parse(String template, T values)` | `String` | Renderiza template Mustache com `values` (Map ou objeto) |
274
+
275
+ ### Exemplos
276
+
277
+ ```groovy
278
+ Map data = [name: "João", points: 150, badge: "Ouro"]
279
+ String html = MustacheUtils.parse("Olá {{name}}, você tem {{points}} pontos e conquistou o badge {{badge}}!", data)
280
+ // → "Olá João, você tem 150 pontos e conquistou o badge Ouro!"
281
+ ```
282
+
283
+ ```groovy
284
+ // Combinando com ExpressionUtils para mensagens ricas
285
+ String template = "Parabéns {{name}}! Você chegou a {{= round(progress) }}% da meta."
286
+ Map params = [name: "Maria", progress: 87.3]
287
+ List errors = []
288
+ String result = MustacheUtils.parse(ExpressionUtils.evaluateInline(template, params, errors), params)
289
+ // → "Parabéns Maria! Você chegou a 87% da meta."
290
+ ```
291
+
292
+ ---
293
+
294
+ ## 9. DiffUtil
295
+
296
+ **FQN:** `com.funifier.engine.util.DiffUtil`
297
+
298
+ Compara dois objetos (Maps, POJOs, listas) e retorna as diferenças em vários formatos. Útil para gerar changelogs de auditoria ou rastrear alterações em documentos customizados.
299
+
300
+ ### Métodos públicos
301
+
302
+ | Método | Retorno | Descrição |
303
+ |--------|---------|-----------|
304
+ | `diff(Object old, Object new)` | `List<Difference>` | Diferenças com config padrão |
305
+ | `diff(Object old, Object new, Config config)` | `List<Difference>` | Diferenças com config customizada |
306
+ | `diffAsMap(Object old, Object new, Config config)` | `Map<String, Map<String, Object>>` | `{path: {old: ..., new: ...}}` |
307
+ | `diffAsJson(Object old, Object new, Config config)` | `String` | JSON compacto do map |
308
+ | `diffAsJsonPretty(Object old, Object new, Config config)` | `String` | JSON indentado do map |
309
+ | `diffAsList(Object old, Object new, Config config)` | `List<Map<String, Object>>` | `[{path:, old:, new:}]` |
310
+ | `diffAsListJson(Object old, Object new, Config config)` | `String` | JSON compacto da lista |
311
+ | `diffAsListJsonPretty(Object old, Object new, Config config)` | `String` | JSON indentado da lista |
312
+ | `buildChangelogFromSequence(List<?> data)` | `List<Map<String, Object>>` | Changelog de sequência de versões |
313
+ | `buildChangelogFromSequence(List<?> data, String sortBy)` | `List<Map<String, Object>>` | Com ordenação por campo |
314
+ | `buildChangelogFromSequence(List<?> data, Config config)` | `List<Map<String, Object>>` | Com config |
315
+ | `buildChangelogFromSequence(List<?> data, String sortBy, Config config)` | `List<Map<String, Object>>` | Com ordenação e config |
316
+
317
+ **`Config` — construtor fluente:**
318
+
319
+ ```groovy
320
+ DiffUtil.Config config = DiffUtil.Config.defaultConfig()
321
+ .ignoreCase(true)
322
+ .ignoreWhitespace(true)
323
+ .ignoreNulls(true)
324
+ .ignoreOrder(false)
325
+ .maxDepth(10)
326
+ .ignoreFields(["updatedAt", "_id"] as Set)
327
+ .ignoreChangesInPaths(["metadata"] as Set)
328
+ ```
329
+
330
+ **`Difference` — campos:**
331
+ - `getPath()` — caminho do campo alterado (ex. `"address.city"`, `"tags[0]"`)
332
+ - `getOldValue()` — valor anterior
333
+ - `getNewValue()` — valor novo
334
+
335
+ ### Exemplos
336
+
337
+ ```groovy
338
+ void trigger(event, entity, player, database) {
339
+ // Recuperar versão anterior do banco
340
+ def oldDoc = database.get(entity._id, "order__c")
341
+
342
+ // Gerar diff entre versão antiga e nova
343
+ DiffUtil.Config cfg = DiffUtil.Config.defaultConfig().ignoreFields(["_id", "updatedAt"] as Set)
344
+ String changes = DiffUtil.diffAsJson(oldDoc, entity, cfg)
345
+
346
+ // Guardar no log de auditoria
347
+ def auditEntry = [orderId: entity._id, changedAt: new Date(), changes: changes]
348
+ database.insert(auditEntry, "order_audit__c")
349
+ }
350
+ ```
351
+
352
+ ```groovy
353
+ // Changelog de uma sequência de versões
354
+ List versions = database.query("{}", "product_history__c")
355
+ List changelog = DiffUtil.buildChangelogFromSequence(versions, "version")
356
+ // Cada item: {campos do objeto + "changes": {path: {old:, new:}}}
357
+ ```
358
+
359
+ ---
360
+
361
+ ## 10. AesCrypt
362
+
363
+ **FQN:** `com.funifier.engine.util.AesCrypt`
364
+
365
+ Criptografia simétrica AES/ECB/PKCS5Padding. Deriva uma chave de 16 bytes via SHA-1 da senha fornecida. Útil para proteger campos sensíveis em coleções customizadas.
366
+
367
+ > 🔐 **Gestão de segredos:** **nunca** hardcode a chave (`secret`) no script versionado. Guarde-a em uma coleção de configuração protegida ou injete via variável de ambiente/configuração de instância. AES/ECB não é autenticado — para alta segurança considere PGP (§12).
368
+
369
+ ### Métodos públicos
370
+
371
+ | Método | Retorno | Descrição |
372
+ |--------|---------|-----------|
373
+ | `encrypt(String strToEncrypt, String secret)` | `String` | Criptografa string e retorna Base64; `null` se erro |
374
+ | `decrypt(String strToDecrypt, String secret)` | `String` | Descriptografa Base64 e retorna string; `null` se erro |
375
+ | `encryptFields(Object object, List<String> fields, String secret)` | `Object` | Criptografa campos específicos (dot-notation JSONPath) no objeto |
376
+ | `decryptFields(Object object, List<String> fields, String secret)` | `Object` | Descriptografa campos específicos no objeto |
377
+
378
+ ### Exemplos
379
+
380
+ ```groovy
381
+ // Criptografar um campo ao salvar
382
+ void trigger(event, entity, player, database) {
383
+ // Ler chave de configuração (NUNCA hardcode aqui)
384
+ def config = database.get("enc_config", "app_config__c")
385
+ String secret = config.aesKey
386
+
387
+ String encrypted = AesCrypt.encrypt(entity.taxId, secret)
388
+ entity.taxId = encrypted
389
+ // entity é salvo pelo motor após o trigger
390
+ }
391
+ ```
392
+
393
+ ```groovy
394
+ // Criptografar múltiplos campos de um Map
395
+ Map payload = [cpf: "123.456.789-09", rg: "12.345.678-9", name: "João"]
396
+ String secret = config.secret
397
+ Map encrypted = AesCrypt.encryptFields(payload, ["cpf", "rg"], secret)
398
+ // encrypted.cpf e encrypted.rg são Base64; encrypted.name permanece em claro
399
+ ```
400
+
401
+ ---
402
+
403
+ ## 11. BCrypt
404
+
405
+ **FQN:** `com.funifier.engine.util.BCrypt`
406
+
407
+ Hash Blowfish de senhas (OpenBSD bcrypt). Padrão para senhas de players na plataforma Funifier.
408
+
409
+ > 🔐 **Gestão de segredos:** `BCrypt` é para hash de senha, não para criptografia reversível. Use `AesCrypt` se precisar recuperar o valor original.
410
+ >
411
+ > ⚠️ `BCrypt` está bloqueado no sandbox do Public Endpoint. Use somente em Trigger ou Scheduler.
412
+
413
+ ### Métodos públicos
414
+
415
+ | Método | Retorno | Descrição |
416
+ |--------|---------|-----------|
417
+ | `hashpw(String password, String salt)` | `String` | Hash da senha com salt; retorna hash no formato `$2a$...` |
418
+ | `gensalt()` | `String` | Gera salt com 10 rounds (padrão) |
419
+ | `gensalt(int log_rounds)` | `String` | Gera salt com N rounds (`4`–`31`); cada `+1` dobra o tempo |
420
+ | `gensalt(int log_rounds, SecureRandom random)` | `String` | Controle total de rounds e fonte aleatória |
421
+ | `checkpw(String plaintext, String hashed)` | `boolean` | Verifica senha em claro contra hash |
422
+
423
+ ### Exemplos
424
+
425
+ ```groovy
426
+ // Criar jogador com senha hasheada (trigger after_create)
427
+ void trigger(event, entity, player, database) {
428
+ if (entity.password != null) {
429
+ entity.password = BCrypt.hashpw(entity.password, BCrypt.gensalt())
430
+ }
431
+ }
432
+ ```
433
+
434
+ ```groovy
435
+ // Verificar senha em login customizado
436
+ boolean valid = BCrypt.checkpw(senhaInformada, hashArmazenado)
437
+ ```
438
+
439
+ > Veja também `guides/java-libraries.md` §7 para mais contexto sobre uso de BCrypt com autenticação de players.
440
+
441
+ ---
442
+
443
+ ## 12. PgpCrypt
444
+
445
+ **FQN:** `com.funifier.engine.util.PgpCrypt`
446
+
447
+ Criptografia assimétrica PGP/RSA (BouncyCastle). Útil para criptografar dados que precisam ser descriptografados por um terceiro (ex: arquivo CSV com dados sensíveis para integração externa).
448
+
449
+ > 🔐 **Gestão de chaves:** as chaves ASCII armored (privada e pública) são strings longas — **nunca** armazene a chave privada em script versionado. Guarde em coleção de configuração restrita ou em variável de ambiente da instância. A chave privada deve ser protegida por passphrase.
450
+
451
+ ### Métodos públicos
452
+
453
+ | Método | Retorno | Descrição |
454
+ |--------|---------|-----------|
455
+ | `encrypt(String publicKey, byte[] data)` | `byte[]` | Criptografa bytes com chave pública ASCII armored |
456
+ | `decrypt(String secretKey, String password, byte[] pgpEncryptedData)` | `byte[]` | Descriptografa com chave secreta ASCII armored e passphrase |
457
+ | `generateKeyPair()` | `KeyPair` | Gera par RSA 1024-bit |
458
+ | `exportKeyPair(OutputStream secretOut, OutputStream publicOut, KeyPair pair, String identity, char[] passPhrase, boolean armor)` | `void` | Exporta par para streams (ASCII armored ou binário) |
459
+ | `getPublicKey(String keydata)` | `PGPPublicKey` | Obtém chave pública de string ASCII armored |
460
+ | `getSecretKey(String keydata)` | `PGPSecretKey` | Obtém chave secreta de string ASCII armored |
461
+ | `getPrivateKey(PGPSecretKey secKey, char[] pass)` | `PGPPrivateKey` | Extrai chave privada de `PGPSecretKey` com passphrase |
462
+
463
+ ### Exemplos
464
+
465
+ ```groovy
466
+ // Gerar e exportar par de chaves (executar uma vez, guardar as strings)
467
+ KeyPair kp = PgpCrypt.generateKeyPair()
468
+ ByteArrayOutputStream sec = new ByteArrayOutputStream()
469
+ ByteArrayOutputStream pub = new ByteArrayOutputStream()
470
+ PgpCrypt.exportKeyPair(sec, pub, kp, "admin@empresa.com", "minha-passphrase".toCharArray(), true)
471
+ String secretKey = sec.toString() // guardar de forma segura
472
+ String publicKey = pub.toString() // pode ser compartilhada
473
+
474
+ // Criptografar dados para envio a integração externa
475
+ byte[] dados = FileUtil.stream2file(inputStream, "relatorio", "csv").bytes
476
+ byte[] encriptado = PgpCrypt.encrypt(publicKey, dados)
477
+
478
+ // Descriptografar (quem tem a chave privada)
479
+ byte[] original = PgpCrypt.decrypt(secretKey, "minha-passphrase", encriptado)
480
+ ```
481
+
482
+ ---
483
+
484
+ ## 13. OtpAuthUtil
485
+
486
+ **FQN:** `com.funifier.engine.util.OtpAuthUtil`
487
+
488
+ Utilitário para autenticação de dois fatores (TOTP / Google Authenticator). Na versão atual do `funifier-service`, o único método ativo é a geração de QR code.
489
+
490
+ > ⚠️ **Métodos TOTP comentados:** `generateSecretKey()`, `getTOTPCode()`, `validateTOTPCode()` e `getGoogleAuthenticatorBarCode()` estão **comentados na fonte atual** e não podem ser chamados. A geração e validação de TOTP deve ser implementada pelo script do desenvolvedor ou via biblioteca externa disponível no classpath.
491
+
492
+ ### Métodos públicos
493
+
494
+ | Método | Retorno | Descrição |
495
+ |--------|---------|-----------|
496
+ | `createQRCode(String barCodeData, OutputStream out, int height, int width)` | `void` | Gera QR code PNG a partir de uma string (ex: URL `otpauth://totp/...`) e escreve no `out` |
497
+
498
+ ### Exemplos
499
+
500
+ ```groovy
501
+ // Gerar QR code de uma URL TOTP (a URL deve ser construída manualmente)
502
+ String account = "usuario@empresa.com"
503
+ String issuer = "MinhaApp"
504
+ String secretKey = "QDWSM3OYBPGTEVSPB5FKVDM3CSNCWHVK" // base32, 20 bytes
505
+
506
+ String barCodeUrl = "otpauth://totp/"
507
+ + java.net.URLEncoder.encode(account, "UTF-8").replace("+", "%20")
508
+ + "?secret=" + java.net.URLEncoder.encode(secretKey, "UTF-8").replace("+", "%20")
509
+ + "&issuer=" + java.net.URLEncoder.encode(issuer, "UTF-8").replace("+", "%20")
510
+
511
+ ByteArrayOutputStream out = new ByteArrayOutputStream()
512
+ OtpAuthUtil.createQRCode(barCodeUrl, out, 300, 300)
513
+ byte[] qrPng = out.toByteArray() // salvar ou servir via Public Endpoint
514
+ ```
515
+
516
+ ---
517
+
518
+ ## 14. HttpUtil
519
+
520
+ **FQN:** `com.funifier.engine.util.HttpUtil`
521
+
522
+ Requisições HTTP síncronas com Apache HttpClient (timeout 5s). Útil para chamar APIs externas a partir de triggers ou schedulers.
523
+
524
+ > ⚠️ Bloqueado no sandbox do Public Endpoint. Use somente em Trigger ou Scheduler. Para o Public Endpoint, prefira `java.net.URL` (§15) ou `Unirest` (ver `guides/java-libraries.md`).
525
+
526
+ ### Métodos públicos
527
+
528
+ | Método | Retorno | Descrição |
529
+ |--------|---------|-----------|
530
+ | `getClientIP(HttpServletRequest request)` | `String` | IP real do cliente (respeita cabeçalhos de proxy: `X-Forwarded-For`, etc.) |
531
+ | `get(String strUrl, String parameters)` | `HttpResponse` | GET com query string; lança `Exception` se falhar |
532
+ | `post(String strUrl, Object obj)` | `HttpResponse` | POST com body JSON serializado; timeout 5s |
533
+
534
+ ### Exemplos
535
+
536
+ ```groovy
537
+ // Chamar API externa em trigger
538
+ void trigger(event, entity, player, database) {
539
+ try {
540
+ def response = HttpUtil.get("https://api.externa.com/v1/dados", "id=" + entity._id)
541
+ int status = response.getStatusLine().getStatusCode()
542
+ if (status == 200) {
543
+ String body = org.apache.http.util.EntityUtils.toString(response.getEntity())
544
+ Map data = JsonUtil.fromJsonToMap(body)
545
+ entity.dadosExternos = data
546
+ }
547
+ } catch (Exception e) {
548
+ println("Erro HTTP: " + e.getMessage())
549
+ }
550
+ }
551
+ ```
552
+
553
+ ```groovy
554
+ // POST para webhook externo
555
+ Map payload = [event: "player_registered", playerId: entity._id]
556
+ HttpUtil.post("https://webhook.empresa.com/funifier", payload)
557
+ ```
558
+
559
+ ---
560
+
561
+ ## 15. UrlUtil
562
+
563
+ **FQN:** `com.funifier.engine.util.UrlUtil`
564
+
565
+ Leitura de URL e limpeza de parâmetros internos Funifier.
566
+
567
+ ### Métodos públicos
568
+
569
+ | Método | Retorno | Descrição |
570
+ |--------|---------|-----------|
571
+ | `readFromUrl(String url)` | `String` | Lê conteúdo da URL como string UTF-8; `null` se erro |
572
+ | `removeFunifierParameteres(String url)` | `String` | Remove parâmetros `funifier_*` da query string; recursivo |
573
+
574
+ ### Exemplos
575
+
576
+ ```groovy
577
+ // Ler arquivo de texto de URL pública
578
+ String csv = UrlUtil.readFromUrl("https://storage.empresa.com/dados.csv")
579
+ if (csv != null) {
580
+ // processar csv
581
+ }
582
+ ```
583
+
584
+ ```groovy
585
+ // Limpar URL de redirecionamento externo
586
+ String url = "https://app.com/page?id=123&funifier_token=abc&other=x"
587
+ String clean = UrlUtil.removeFunifierParameteres(url)
588
+ // → "https://app.com/page?id=123&other=x"
589
+ ```
590
+
591
+ ---
592
+
593
+ ## 16. ExcelUtil
594
+
595
+ **FQN:** `com.funifier.engine.util.ExcelUtil`
596
+
597
+ Geração de arquivo XLSX (Excel) a partir de uma lista de Maps. Os cabeçalhos são extraídos das chaves do primeiro Map; as colunas são auto-dimensionadas.
598
+
599
+ ### Métodos públicos
600
+
601
+ | Método | Retorno | Descrição |
602
+ |--------|---------|-----------|
603
+ | `generateExcelFromObjects(List<Map<String, Object>> listRows, String sheetName)` | `byte[]` | Gera XLSX; retorna `null` se lista vazia ou erro |
604
+
605
+ ### Exemplos
606
+
607
+ ```groovy
608
+ // Gerar Excel de relatório a partir de aggregate
609
+ void execute() {
610
+ def rows = manager.getJongoConnection()
611
+ .getCollection("achievement")
612
+ .aggregate("{$match: {type: 0}}")
613
+ .and("{$group: {_id: '$player', total: {$sum: '$total'}}}")
614
+ .as(HashMap.class)
615
+ .toList()
616
+
617
+ List<Map<String, Object>> data = rows.collect { row ->
618
+ [player: row._id, pontos: row.total]
619
+ }
620
+
621
+ byte[] xlsx = ExcelUtil.generateExcelFromObjects(data, "Pontuação")
622
+
623
+ // Salvar o arquivo via FileUtil para servir ou enviar
624
+ // (ou usar FileUtil + UrlUtil se precisar enviar externamente)
625
+ }
626
+ ```
627
+
628
+ ---
629
+
630
+ ## 17. PaginationUtil
631
+
632
+ **FQN:** `com.funifier.engine.util.PaginationUtil`
633
+
634
+ Paginação de results de aggregate Jongo. Usa `$facet` internamente para calcular total e página de uma só vez.
635
+
636
+ ### Métodos públicos
637
+
638
+ | Método | Retorno | Descrição |
639
+ |--------|---------|-----------|
640
+ | `getPageResult(Aggregate a, String range, long _skip, long _limit)` | `Map<String, Object>` | Pagina aggregate; swallows erros → retorna lista vazia |
641
+ | `getPageResultThrowsException(Aggregate a, String range, long _skip, long _limit)` | `Map<String, Object>` | Pagina aggregate; lança exceção em erros de sintaxe |
642
+ | `getRange(String rangeHeader, long skip, long limit)` | `long[]` | Parseia header `Range: items=skip-limit` → `[skip, limit]` |
643
+
644
+ **Constantes:** `PaginationUtil.RESULT_KEY = "result"`, `COUNT_KEY = "count"`, `RANGE_KEY = "range"`
645
+
646
+ **Map retornado por `getPageResult`:**
647
+ - `"result"` → `List<Object>` com os documentos da página
648
+ - `"count"` → total de documentos (antes da paginação)
649
+ - `"range"` → string no formato `"items skip-limit/count"`
650
+
651
+ ### Exemplos
652
+
653
+ ```groovy
654
+ // Paginar aggregate de players em um Public Endpoint
655
+ def agg = manager.getJongoConnection()
656
+ .getCollection("player")
657
+ .aggregate("{$match: {active: true}}")
658
+
659
+ String rangeHeader = request.getHeader("Range") // "items=0-24"
660
+ Map page = PaginationUtil.getPageResult(agg, rangeHeader, 0L, 25L)
661
+
662
+ List players = page[PaginationUtil.RESULT_KEY]
663
+ Object total = page[PaginationUtil.COUNT_KEY]
664
+ ```
665
+
666
+ ---
667
+
668
+ ## 18. FileUtil
669
+
670
+ **FQN:** `com.funifier.engine.util.FileUtil`
671
+
672
+ Conversão de `InputStream` em arquivo temporário. O arquivo é marcado como `deleteOnExit`.
673
+
674
+ ### Métodos públicos
675
+
676
+ | Método | Retorno | Descrição |
677
+ |--------|---------|-----------|
678
+ | `stream2file(InputStream inStream, String fileName, String extension)` | `File` | Cria arquivo temp `fileName-<uuid>.<extension>` com conteúdo do stream |
679
+
680
+ ### Exemplos
681
+
682
+ ```groovy
683
+ // Processar CSV recebido como stream
684
+ File tempFile = FileUtil.stream2file(csvInputStream, "import", "csv")
685
+ String content = tempFile.text // Groovy sugar — lê todo o arquivo
686
+
687
+ // Converter para byte[] para PgpCrypt ou ExcelUtil
688
+ byte[] bytes = tempFile.bytes
689
+ ```
690
+
691
+ ---
692
+
693
+ ## 19. DateUtil — Cross-link
694
+
695
+ `DateUtil` (`com.funifier.engine.util.DateUtil`) pertence ao tema de **datas e períodos Funifier**. A API completa — `parse`, `fromKeyword`, `fromKeyword(String)`, formatação, aritmética de datas — está documentada em:
696
+
697
+ - **Skill:** `funifier-date-handling` (via skill router)
698
+ - **Guia Java:** `guides/java-libraries.md` §4
699
+
700
+ **Não** duplique a API de `DateUtil` aqui. Para uso de datas em triggers, schedulers e aggregates, consulte esses documentos.
701
+
702
+ ---
703
+
704
+ ## 20. Internos do Motor — OUT
705
+
706
+ As classes abaixo são usadas pelo próprio motor Funifier e **não devem ser instanciadas ou chamadas diretamente** pelo código do desenvolvedor. São listadas aqui apenas para referência — caso você as encontre em stack traces ou no classpath.
707
+
708
+ | Classe | Papel no motor |
709
+ |--------|----------------|
710
+ | `FunifierMarshaller` | Conversão BSON ↔ objetos; usado pelo motor nas queries e aggregates |
711
+ | `FunifierBSONCallback` | Parser BSON personalizado para o `fromFunifierBson` do `JsonUtil` |
712
+ | `FunifierBsonWithParamsCallback` | Parser BSON com substituição de `$param` |
713
+ | `Entity` | Superclasse de entidades do motor (PlayerStatus, etc.) |
714
+ | `OptionModel` | Modelo de opção genérico para campos de seleção |
715
+ | `SearchUtil` | Utilitário de busca textual interno do motor |
716
+ | `MysqlUtil` | Acesso a MySQL em integrações legadas (não usar) |
717
+ | `HttpClientFunifier` | Cliente HTTP interno do motor |
718
+ | `X509Util` | Utilitário de certificado X.509 para infraestrutura TLS interna |