funifier-mcp 0.2.26 → 0.2.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/rules/funifier.mdc +38 -41
- package/.github/copilot-instructions.md +38 -41
- package/AGENTS.md +56 -49
- package/README.md +40 -22
- package/datasource-funifier-docs/.coverage.json +326 -0
- package/datasource-funifier-docs/.validation.json +593 -0
- package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
- package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
- package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
- package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
- package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
- package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
- package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
- package/datasource-funifier-docs/knowledge/index.md +4 -1
- package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
- package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
- package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
- package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
- package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
- package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
- package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
- package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
- package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
- package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
- package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
- package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
- package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
- package/datasource-funifier-docs/knowledge/modules/folder.md +935 -280
- package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
- package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
- package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
- package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
- package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
- package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
- package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
- package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
- package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
- package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
- package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
- package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
- package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
- package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
- package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
- package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
- package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
- package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
- package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
- package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
- package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
- package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
- package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
- package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
- package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
- package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
- package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +42 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/init.test.js +74 -3
- package/dist/cli/init.test.js.map +1 -1
- package/dist/cli/persona.d.ts +3 -0
- package/dist/cli/persona.d.ts.map +1 -0
- package/dist/cli/persona.js +25 -0
- package/dist/cli/persona.js.map +1 -0
- package/dist/mcp/bundle.js +119 -93
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/resources/documentation.d.ts +1 -1
- package/dist/mcp/resources/documentation.d.ts.map +1 -1
- package/dist/mcp/resources/documentation.js +39 -3
- package/dist/mcp/resources/documentation.js.map +1 -1
- package/dist/mcp/tools/connect.d.ts.map +1 -1
- package/dist/mcp/tools/connect.js +18 -8
- package/dist/mcp/tools/connect.js.map +1 -1
- package/dist/mcp/tools/database.d.ts.map +1 -1
- package/dist/mcp/tools/database.js +59 -47
- package/dist/mcp/tools/database.js.map +1 -1
- package/dist/mcp/tools/database.test.js +2 -2
- package/dist/mcp/tools/database.test.js.map +1 -1
- package/dist/mcp/tools/delete.d.ts.map +1 -1
- package/dist/mcp/tools/delete.js +13 -3
- package/dist/mcp/tools/delete.js.map +1 -1
- package/dist/mcp/tools/execute.d.ts.map +1 -1
- package/dist/mcp/tools/execute.js +20 -9
- package/dist/mcp/tools/execute.js.map +1 -1
- package/dist/mcp/tools/folder.d.ts.map +1 -1
- package/dist/mcp/tools/folder.js +22 -12
- package/dist/mcp/tools/folder.js.map +1 -1
- package/dist/mcp/tools/get.d.ts.map +1 -1
- package/dist/mcp/tools/get.js +16 -6
- package/dist/mcp/tools/get.js.map +1 -1
- package/dist/mcp/tools/index.d.ts +1 -1
- package/dist/mcp/tools/index.d.ts.map +1 -1
- package/dist/mcp/tools/index.js +3 -1
- package/dist/mcp/tools/index.js.map +1 -1
- package/dist/mcp/tools/list.d.ts.map +1 -1
- package/dist/mcp/tools/list.js +38 -14
- package/dist/mcp/tools/list.js.map +1 -1
- package/dist/mcp/tools/logs.d.ts.map +1 -1
- package/dist/mcp/tools/logs.js +15 -5
- package/dist/mcp/tools/logs.js.map +1 -1
- package/dist/mcp/tools/save.d.ts.map +1 -1
- package/dist/mcp/tools/save.js +14 -4
- package/dist/mcp/tools/save.js.map +1 -1
- package/dist/mcp/tools/save.test.js +3 -3
- package/dist/mcp/tools/save.test.js.map +1 -1
- package/dist/mcp/tools/search-docs.d.ts +3 -0
- package/dist/mcp/tools/search-docs.d.ts.map +1 -0
- package/dist/mcp/tools/search-docs.js +102 -0
- package/dist/mcp/tools/search-docs.js.map +1 -0
- package/package.json +6 -2
- package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
- package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
- package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
- package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
- package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
- package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
- package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
- package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
- package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
- package/skills/funifier/SKILL.md +88 -0
- package/skills/funifier/references/configure-security.md +96 -0
- package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
- package/skills/funifier/references/create-aggregate.md +144 -0
- package/skills/funifier/references/create-challenge.md +116 -0
- package/skills/funifier/references/create-competition.md +98 -0
- package/skills/funifier/references/create-crossword.md +574 -0
- package/skills/funifier/references/create-custom-object.md +91 -0
- package/skills/funifier/references/create-custom-page.md +135 -0
- package/skills/funifier/references/create-folder.md +104 -0
- package/skills/funifier/references/create-lastmile.md +643 -0
- package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
- package/skills/funifier/references/create-level.md +94 -0
- package/skills/funifier/references/create-lottery.md +913 -0
- package/skills/funifier/references/create-mystery.md +769 -0
- package/skills/funifier/references/create-notification.md +75 -0
- package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
- package/skills/funifier/references/create-quiz.md +98 -0
- package/skills/funifier/references/create-scheduler.md +141 -0
- package/skills/funifier/references/create-story.md +636 -0
- package/skills/funifier/references/create-swap.md +95 -0
- package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
- package/skills/funifier/references/create-virtual-good.md +96 -0
- package/skills/funifier/references/create-webhook.md +72 -0
- package/skills/funifier/references/create-websocket.md +71 -0
- package/skills/funifier/references/create-widget.md +76 -0
- package/skills/funifier/references/debug.md +87 -0
- package/skills/funifier/references/help.md +81 -0
- package/skills/funifier/references/implement-frontend.md +106 -0
- package/skills/funifier/references/import-csv.md +75 -0
- package/skills/funifier/references/manage-player.md +82 -0
- package/skills/funifier/references/manage-team.md +76 -0
- package/skills/funifier/references/upload-file.md +91 -0
- package/skills/funifier-create-aggregate/SKILL.md +0 -127
- package/skills/funifier-create-challenge/SKILL.md +0 -88
- package/skills/funifier-create-custom-page/SKILL.md +0 -127
- package/skills/funifier-create-level/SKILL.md +0 -87
- package/skills/funifier-create-quiz/SKILL.md +0 -87
- package/skills/funifier-create-scheduler/SKILL.md +0 -127
- package/skills/funifier-create-virtual-good/SKILL.md +0 -87
- package/skills/funifier-debug/SKILL.md +0 -92
- package/skills/funifier-help/SKILL.md +0 -86
- package/skills/funifier-implement-frontend/SKILL.md +0 -90
- package/skills/funifier-index/SKILL.md +0 -58
|
@@ -1,30 +1,655 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `csv`
|
|
2
2
|
|
|
3
|
-
**API
|
|
3
|
+
**API Base:** `/v3/csv`
|
|
4
|
+
**Coleções MongoDB:** `csv_config__c` (configurações de formato/importação), `csv_log__c` (logs de execução), `csv_format__c` (órfã — ver §7)
|
|
5
|
+
**Controller:** `com.funifier.rest.v3.rest.CsvRest`
|
|
6
|
+
**Manager:** `com.funifier.engine.csv.CsvManager` (1981 linhas, sem Repository/Dao dedicado — acesso direto via Jongo)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
> Documentação produzida por engenharia reversa do código-fonte de `funifier-service`. Reflete o **comportamento real** das classes, não o schema declarado nem a documentação anterior.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
---
|
|
8
11
|
|
|
9
|
-
##
|
|
12
|
+
## 1. Visão Geral
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
- Para exportar relatórios de desempenho
|
|
13
|
-
- Para migrar dados de sistemas externos
|
|
14
|
-
- Para importação segura via FTP com criptografia
|
|
14
|
+
O módulo `csv` é o **motor de importação e exportação em massa** da plataforma. Ele converte arquivos CSV (texto plano ou upload multipart) em documentos MongoDB e converte resultados de aggregations MongoDB de volta em CSV/Excel.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Papel arquitetural — o módulo abriga **dois subsistemas de importação independentes**, que compartilham apenas utilitários de parsing:
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
**Método:** POST
|
|
20
|
-
**Endpoint:** `/v3/csv`
|
|
18
|
+
1. **Import como `action_log`** (`POST /v3/csv/import` → `importActionLogRows`): orientado por um documento `csv_config__c` que define mapeamento de colunas (`match`), uma `action` alvo e um script Groovy opcional. Cada linha vira um `ActionLog` rastreado via `ActionManager.trackWithRestrictions`. **Síncrono**, parser ingênuo.
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
**Método:** GET
|
|
24
|
-
**Endpoint:** `/v3/csv`
|
|
20
|
+
2. **Bulk/merge em coleção arbitrária** (`POST /v3/csv/{collection}/bulk` e `/merge` → `insertBulk`/`mergeBulk`): orientado pela lista `fields[]` do `csv_config__c` (tipagem de campos). Persiste documentos em **qualquer** coleção nomeada na URL. **Assíncrono por padrão**, processado em lotes, cancelável, com triggers. Usa parser Apache Commons CSV.
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
Além disso oferece:
|
|
27
23
|
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
24
|
+
- **Exportação** (`POST /v3/csv/{collection}/aggregate` e `/aggregate/to_excel`): executa um pipeline de aggregation arbitrário sobre uma coleção e devolve CSV (`text/plain`) ou planilha `.xls` (`application/octet-stream`).
|
|
25
|
+
- **Parsing/preview** (`/read`, `/split`, `/split/file`): converte CSV em linhas JSON sem persistir.
|
|
26
|
+
- **Descriptografia PGP** de uploads (campo `upload_encrypted` no config + `CryptTransferKey`).
|
|
27
|
+
|
|
28
|
+
Problemas que resolve: carga inicial de jogadores/times, ingestão recorrente de eventos como `action_log`, migração de sistemas externos, e geração de relatórios CSV/Excel a partir de aggregations.
|
|
29
|
+
|
|
30
|
+
Relação com outros módulos:
|
|
31
|
+
|
|
32
|
+
- `action` — `/import` chama `ActionManager.findActionById` (resolve a action e o tipo de cada atributo) e `ActionManager.trackWithRestrictions`; bulk em `action_log` usa `ActionManager.track`.
|
|
33
|
+
- `trigger` — bulk/merge dispara `csv_before_bulk`/`csv_after_bulk`; saves em coleções comuns disparam `before_create`/`after_create`.
|
|
34
|
+
- `crypt` — `CryptManager.findTransferKeyActive` + `decryptTransfer` (PGP) para uploads criptografados.
|
|
35
|
+
- Qualquer coleção — bulk grava, e aggregate lê, **qualquer** coleção nomeada na URL (sem allowlist). Ver §8.
|
|
36
|
+
|
|
37
|
+
> **Não há FTP.** A documentação anterior afirmava "importação via FTP" — não existe nenhum código de FTP no módulo. As únicas classes que referenciam `CsvManager` são `CsvRest`, `CsvAsyncProcessor` e `ManagerFactory`.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 2. Arquitetura e Fluxos
|
|
42
|
+
|
|
43
|
+
### 2.1 Classes envolvidas
|
|
44
|
+
|
|
45
|
+
| Classe | Papel |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `com.funifier.rest.v3.rest.CsvRest` | Controller REST v3 (`/v3/csv`) — 12 endpoints |
|
|
48
|
+
| `com.funifier.engine.csv.CsvManager` | Manager monolítico: parsing, import, bulk, export, Groovy, criptografia |
|
|
49
|
+
| `com.funifier.engine.csv.CsvAsyncProcessor` | `Runnable` de thread única que drena uma fila de jobs `insertBulk`/`mergeBulk` assíncronos |
|
|
50
|
+
| `com.funifier.engine.csv.CsvConfig` | POJO da configuração (`csv_config__c`) — **modela só parte** do documento (ver §3.1) |
|
|
51
|
+
| `com.funifier.engine.csv.CsvLog` | POJO do log de execução (`csv_log__c`) |
|
|
52
|
+
|
|
53
|
+
### 2.2 Os dois parsers de CSV (divergência crítica)
|
|
54
|
+
|
|
55
|
+
O módulo tem **dois parsers diferentes**, e o caminho escolhido depende do endpoint:
|
|
56
|
+
|
|
57
|
+
| Aspecto | `readRows` (linha 278) | `splitCsv` (linha 945) |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| Usado por | `/read`, `/import` | `/split`, `/split/file`, todo bulk/merge (via `convertCsvInObjects`) |
|
|
60
|
+
| Engine | `line.split(delimiter)` manual | Apache Commons CSV (`CSVParser`) |
|
|
61
|
+
| Aspas / escape | **Não suporta** (split cego) | Suporta (`withQuote('"')`) |
|
|
62
|
+
| Cabeçalho | Controlado por `header` (param/config) | **Sempre** `withFirstRecordAsHeader()` — `header` é ignorado |
|
|
63
|
+
| Normalização do header | NFD → remove não-ASCII → remove não-`[0-9a-zA-Z_]` → `toLowerCase().trim()` | Nenhuma — usa o nome literal |
|
|
64
|
+
| Delimitadores | `comma`, `semicolon`, `tab` (**sem pipe**) | `comma`, `semicolon`, `tab`, `pipe` |
|
|
65
|
+
| Linha curta (menos colunas que o header) | **Descartada silenciosamente** (`fields.length >= names.length`) | Tratada pelo Commons CSV |
|
|
66
|
+
|
|
67
|
+
> Consequência prática: `/import` e `/read` quebram com valores que contêm o delimitador entre aspas; bulk/merge não. E `pipe` só funciona em bulk/merge/split.
|
|
68
|
+
|
|
69
|
+
### 2.3 Pipeline — `POST /v3/csv/import` (import como action_log)
|
|
70
|
+
|
|
71
|
+
Método-raiz: `importActionLogRows(String id, String content)` (linha 119). **Sempre síncrono** — o query param `async` é aceito e **ignorado** (o caminho assíncrono está comentado em `CsvRest.java:69-78`).
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
[1] config = find(id) // lê csv_config__c[id]
|
|
75
|
+
[2] cria CsvLog(collection="action_log", status="started")
|
|
76
|
+
if(config.log) insertLog() // só persiste log se config.log == true
|
|
77
|
+
[3] if(config.script.length > 10):
|
|
78
|
+
script = getScript(config); clazz = compile(script) // Groovy sandbox (§2.6)
|
|
79
|
+
[4] keys = config.match.keySet()
|
|
80
|
+
if(config.action) action = ActionManager.findActionById(config.action)
|
|
81
|
+
[5] rows = readRows(config.delimiter, config.header, -1, content) // parser ingênuo (§2.2)
|
|
82
|
+
[6] para cada row:
|
|
83
|
+
[6.1] if(config.log && config.log_field): row[log_field] = importlog._id
|
|
84
|
+
[6.2] monta `match` (de→para):
|
|
85
|
+
para cada key em config.match:
|
|
86
|
+
coluna = config.match[key] // valor do match = NOME da coluna CSV
|
|
87
|
+
val = row[coluna]
|
|
88
|
+
se action e attribute(key).type == "Number": val = Double.parseDouble(val)
|
|
89
|
+
// exceção de parse → engolida em `err`, a linha continua
|
|
90
|
+
match[key] = val
|
|
91
|
+
[6.3] if(clazz): executor(clazz, config, row, match) // Groovy prepare(), timeout 5s
|
|
92
|
+
[6.4] if(config.action):
|
|
93
|
+
log = new ActionLog()
|
|
94
|
+
log.actionId = config.action
|
|
95
|
+
log.time = match["time"] (se Date)
|
|
96
|
+
log.userId = match["userId"] (se String)
|
|
97
|
+
log.id = match["_id"] (se String)
|
|
98
|
+
match.remove("time"); match.remove("userId"); match.remove("_id")
|
|
99
|
+
log.attributes = match
|
|
100
|
+
ActionManager.trackWithRestrictions(log, null)
|
|
101
|
+
[7] importlog.status="finished"; rows=totalrows
|
|
102
|
+
if(config.log) insertLog()
|
|
103
|
+
return { action, log, all (sempre vazio), err }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> Se `config.action` for nulo, **nada é persistido**: as linhas são lidas e o script roda, mas não há `ActionLog` criado. `/import` só sabe importar `action_log`.
|
|
107
|
+
|
|
108
|
+
```mermaid
|
|
109
|
+
flowchart LR
|
|
110
|
+
A[POST /v3/csv/import<br/>body = CSV] --> B[find csv_config__c]
|
|
111
|
+
B --> C[readRows<br/>parser ingenuo]
|
|
112
|
+
C --> D{para cada linha}
|
|
113
|
+
D --> E[match de->para<br/>via config.match]
|
|
114
|
+
E --> F{config.script?}
|
|
115
|
+
F -- sim --> G[Groovy prepare<br/>timeout 5s]
|
|
116
|
+
F -- nao --> H
|
|
117
|
+
G --> H{config.action?}
|
|
118
|
+
H -- sim --> I[ActionLog +<br/>trackWithRestrictions]
|
|
119
|
+
H -- nao --> J[nada persistido]
|
|
120
|
+
I --> K[CsvLog finished]
|
|
121
|
+
J --> K
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 2.4 Pipeline — `POST /v3/csv/{collection}/bulk` e `/merge`
|
|
125
|
+
|
|
126
|
+
`insertBulk`/`mergeBulk` (linhas 613/647) criam o `CsvLog` (status `started`) e então:
|
|
127
|
+
|
|
128
|
+
- `async=false` → executa `insertBulkSync` **na thread da requisição** (síncrono).
|
|
129
|
+
- qualquer outro valor (inclusive ausente) → enfileira na fila de `CsvAsyncProcessor` (**assíncrono é o padrão**).
|
|
130
|
+
|
|
131
|
+
> A fila do `CsvAsyncProcessor` é **única por instância de `CsvManager`**, drenada por **uma só thread** com poll de 1s. Imports assíncronos concorrentes da mesma gamificação são **serializados**.
|
|
132
|
+
|
|
133
|
+
`insertBulkSync(collection, format, delimiter, csv, values, logId)` (linha 690):
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
[1] log = findLog(logId)
|
|
137
|
+
[2] dispara trigger "csv_before_bulk" (context.extra["log"] = log)
|
|
138
|
+
[3] modo merge: se csv vazio e values presente:
|
|
139
|
+
csv = values["source"]
|
|
140
|
+
merge = getFieldsPathAndValue(values["upsert"]) // campos-folha do upsert
|
|
141
|
+
[4] guard: log != null && collection.length > 3 && csv.length > 0
|
|
142
|
+
[5] linhas = contagem total (para percent)
|
|
143
|
+
[6] formats = reverseFormats(collection) // infere tipos de UM doc amostra (findOne)
|
|
144
|
+
⊕ getFormats(format) // fields[] do csv_config__c[format] (override)
|
|
145
|
+
config = find(format)
|
|
146
|
+
[7] LOTE = 100 linhas por vez (o comentario "1000" esta desatualizado):
|
|
147
|
+
acumula linhas, re-prepende o cabecalho a cada lote;
|
|
148
|
+
a cada lote:
|
|
149
|
+
insertBulkSyncHelper(lote)
|
|
150
|
+
se findLog(logId).status == "canceled":
|
|
151
|
+
log.status = "canceled"; finaliza; RETURN
|
|
152
|
+
percent = (total*100)/linhas // divisao inteira -> truncado
|
|
153
|
+
insertLog(log)
|
|
154
|
+
[8] log.status = "finished"; percent = 100
|
|
155
|
+
[9] dispara trigger "csv_after_bulk" // só no caminho de sucesso
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`insertBulkSyncHelper` (linha 828) por documento convertido:
|
|
159
|
+
|
|
160
|
+
```
|
|
161
|
+
- se nao tem _id -> _id = Guid.newShortGuid()
|
|
162
|
+
- se collection == "action_log":
|
|
163
|
+
converte em ActionLog (time = now se nulo) -> ActionManager.track(a)
|
|
164
|
+
- senao:
|
|
165
|
+
trigger before_create -> collection.save(object) -> trigger after_create
|
|
166
|
+
(exceptions -> e.printStackTrace(), NAO gravadas no CsvLog)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```mermaid
|
|
170
|
+
flowchart LR
|
|
171
|
+
A[POST /{collection}/bulk] --> B[CsvLog started]
|
|
172
|
+
B --> C{async?}
|
|
173
|
+
C -- false --> D[insertBulkSync<br/>thread da request]
|
|
174
|
+
C -- padrao --> E[fila CsvAsyncProcessor<br/>1 thread]
|
|
175
|
+
E --> D
|
|
176
|
+
D --> F[trigger csv_before_bulk]
|
|
177
|
+
F --> G[lotes de 100 linhas]
|
|
178
|
+
G --> H[convertCsvInObjects<br/>Apache Commons CSV]
|
|
179
|
+
H --> I{collection == action_log?}
|
|
180
|
+
I -- sim --> J[ActionManager.track]
|
|
181
|
+
I -- nao --> K[before_create -> save -> after_create]
|
|
182
|
+
J --> L{status == canceled?}
|
|
183
|
+
K --> L
|
|
184
|
+
L -- sim --> M[CsvLog canceled<br/>RETURN]
|
|
185
|
+
L -- nao --> N[atualiza percent]
|
|
186
|
+
N --> G
|
|
187
|
+
G --> O[CsvLog finished]
|
|
188
|
+
O --> P[trigger csv_after_bulk]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 2.5 Pipeline — exportação (`/aggregate`, `/aggregate/to_excel`)
|
|
192
|
+
|
|
193
|
+
`aggregate` (linha 410) / `aggregateExcel` (linha 498):
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
[1] guard: collection != null && collection.length > 3
|
|
197
|
+
[2] roda o pipeline (body = lista de stages) na coleção, com allowDiskUse(true)
|
|
198
|
+
se body vazio -> usa {$match:{}} (todos os documentos)
|
|
199
|
+
[3] pagina via header Range (default offset 0, page 100) -> PaginationUtil
|
|
200
|
+
[4] achata cada doc em pares path/valor (getFieldsPathAndValue)
|
|
201
|
+
[5] formats = reverseFormats(collection) ⊕ getFormats(format)
|
|
202
|
+
[6] convertObjectFieldsToSampleCsv(...) -> CSV (cabeçalho ordenado alfabeticamente)
|
|
203
|
+
[7a] aggregate -> devolve text/plain + header Content-Range
|
|
204
|
+
[7b] aggregateExcel-> csvToExcel (Apache POI XSSF) -> arquivo .xls temporário
|
|
205
|
+
delimitador FORÇADO para vírgula; nome da planilha = name|collection
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 2.6 Sandbox Groovy (`/import` com `config.script`)
|
|
209
|
+
|
|
210
|
+
`getScript` (linha 1776) envolve `config.script` numa classe `FunifierCsv` com:
|
|
211
|
+
|
|
212
|
+
- `@TimedInterrupt(value = 5L, unit = SECONDS)` — interrupção dura no bytecode.
|
|
213
|
+
- Imports pré-carregados de entidades Funifier (`Player`, `Team`, `Achievement`, `ActionLog`, etc.) e utilitários (`JsonUtil`, `HttpUtil`, `DateUtil`...).
|
|
214
|
+
- Injeção de `manager` (`ManagerFactory` completo) e `database` (`GameDao`).
|
|
215
|
+
- `println(msg)` redirecionado para uma lista `output`.
|
|
216
|
+
|
|
217
|
+
`compile` (linha 1872) usa `SecureASTCustomizer` com **whitelist de tokens** (operadores aritméticos, comparações, lógicos, colchetes) + `TriggerExpressionChecker`. `executor` (linha 1921) roda em `ExecutorService` de thread única com `future.get(5, SECONDS)`.
|
|
218
|
+
|
|
219
|
+
**O script do usuário deve definir o método `prepare(config, row, match)`** — é o que `executor` invoca. Exceções de compilação/execução/timeout são adicionadas à lista `err` e **a linha continua sendo processada**.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 3. Estrutura dos Objetos
|
|
224
|
+
|
|
225
|
+
### 3.1 `csv_config__c` — documento de configuração
|
|
226
|
+
|
|
227
|
+
Modelado **parcialmente** por `CsvConfig` (`@JsonIgnoreProperties(ignoreUnknown=true)`). O runtime lê campos adicionais direto do `HashMap` que **não existem no POJO**.
|
|
228
|
+
|
|
229
|
+
| Campo | Tipo | Padrão | Usado em runtime | Descrição |
|
|
230
|
+
|---|---|---|---|---|
|
|
231
|
+
| `_id` | String | — | sim | Id do config; é o valor passado em `config=`/`format=` |
|
|
232
|
+
| `delimiter` | String | `comma` | `/import` | `comma`/`semicolon`/`tab` (pipe **não** vale aqui) |
|
|
233
|
+
| `header` | String | — | `/import` | `"true"` → primeira linha é cabeçalho (comparação textual com `"true"`) |
|
|
234
|
+
| `action` | String | — | `/import` | Id da `action`; define o tipo de import como `action_log` |
|
|
235
|
+
| `match` | Map | — | `/import` | de→para: **chave** = campo destino do ActionLog, **valor** = nome da coluna CSV |
|
|
236
|
+
| `script` | String | — | `/import` | Corpo Groovy; só compila se `length > 10` |
|
|
237
|
+
| `log` | boolean | `false` | ambos | Liga a gravação de `csv_log__c` e a injeção de `log_field` |
|
|
238
|
+
| `log_field` | String | — | ambos | Nome do campo onde o `_id` do log é injetado em cada linha |
|
|
239
|
+
| `title` | String | — | **não** | Declarado no POJO, nunca lido — rótulo de UI |
|
|
240
|
+
| `sample` | Map | — | **não** | Declarado no POJO, nunca lido — preview de UI |
|
|
241
|
+
|
|
242
|
+
**Campos lidos como HashMap cru (fora do POJO `CsvConfig`):**
|
|
243
|
+
|
|
244
|
+
| Campo | Tipo | Lido por | Descrição |
|
|
245
|
+
|---|---|---|---|
|
|
246
|
+
| `fields` | Array de objetos | `getFormats` (linha 1088) | Definições de tipagem/cabeçalho para bulk/merge/export (ver §3.2) |
|
|
247
|
+
| `upload_encrypted` | boolean (string-parseado) | `isEncrypted` (linha 1072) | Se `true`, uploads multipart são descriptografados via PGP |
|
|
248
|
+
|
|
249
|
+
> **Não existe endpoint de CRUD para `csv_config__c` neste módulo.** Os documentos de config são criados pela API genérica de banco/custom object gravando na coleção `csv_config__c`. O endpoint `/format` grava em outra coleção (ver §7).
|
|
250
|
+
|
|
251
|
+
### 3.2 `fields[]` — definição de formato (subentidade do config)
|
|
252
|
+
|
|
253
|
+
Cada item do array `fields` descreve uma coluna para os fluxos de bulk/merge/export:
|
|
254
|
+
|
|
255
|
+
| Chave | Tipo | Descrição |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `path` | String | Caminho do campo no documento (`a.b`, `lista[0].x`) — usado para montar/ler o objeto |
|
|
258
|
+
| `name` | String | Nome "amigável" no cabeçalho CSV; mapeado para/desde `path` por `getPathFromName`/`getNameFromPath` |
|
|
259
|
+
| `type` | String | `string` (default), `integer`, `double`, `boolean`, `date` |
|
|
260
|
+
| `format` | String | Padrão de data quando `type=date` (default `yyyy-MM-dd HH:mm:ss`) |
|
|
261
|
+
| `default` | qualquer | Valor padrão aplicado quando a coluna está ausente/`null` (tipado conforme `type`) |
|
|
262
|
+
|
|
263
|
+
Coerção de tipo (linhas 1281-1297): `integer`→`Integer.parseInt`, `double`→`Double.parseDouble`, `boolean`→`Boolean.parseBoolean`, `date`→`SimpleDateFormat.parse`. Falha de parse → `printStackTrace`, o campo daquela linha fica sem valor convertido.
|
|
264
|
+
|
|
265
|
+
`reverseFormats` (linha 1025) **infere** tipos lendo **um único** documento amostra (`findOne`) da coleção destino; `getFormats` (definições explícitas do `fields[]`) **sobrescreve** a inferência.
|
|
266
|
+
|
|
267
|
+
### 3.3 `csv_log__c` — log de execução
|
|
268
|
+
|
|
269
|
+
POJO `CsvLog`. Persistido somente quando aplicável (import: só se `config.log`; bulk/merge: sempre).
|
|
270
|
+
|
|
271
|
+
| Campo | Tipo | Descrição |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `_id` | String | `Guid.shortTimeMillis()` — devolvido na resposta como `log` |
|
|
274
|
+
| `config` | String | Id do config/format usado |
|
|
275
|
+
| `collection` | String | Coleção destino (`action_log` no import) |
|
|
276
|
+
| `status` | String | `started` → `finished` \| `canceled` |
|
|
277
|
+
| `start` | Date | Início |
|
|
278
|
+
| `finish` | Date | Fim (em sucesso ou cancelamento) |
|
|
279
|
+
| `percent` | Double | Progresso 0–100, **truncado** (divisão inteira `total*100/linhas`) |
|
|
280
|
+
| `rows` | long | Total de linhas processadas |
|
|
281
|
+
| `extra` | Map | Campo livre (não preenchido pelo fluxo padrão) |
|
|
282
|
+
|
|
283
|
+
Constantes: `STATUS_STARTED="started"`, `STATUS_CANCELED="canceled"`, `STATUS_FINISHED="finished"`. O status intermediário `"running"` aparece **comentado** no código e nunca é gravado.
|
|
284
|
+
|
|
285
|
+
```mermaid
|
|
286
|
+
stateDiagram-v2
|
|
287
|
+
[*] --> started: insertBulk / importActionLogRows
|
|
288
|
+
started --> finished: todos os lotes processados
|
|
289
|
+
started --> canceled: status set p/ "canceled" entre lotes
|
|
290
|
+
finished --> [*]
|
|
291
|
+
canceled --> [*]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 4. Endpoints
|
|
297
|
+
|
|
298
|
+
Todos sob `/v3/csv`, autenticação **Bearer token** (`AuthBean`). Não há checagem de papel/permissão adicional no controller.
|
|
299
|
+
|
|
300
|
+
### `POST /v3/csv/read`
|
|
301
|
+
| Aspecto | Detalhe |
|
|
302
|
+
|---|---|
|
|
303
|
+
| Finalidade | Faz preview/parse de um CSV em linhas JSON. **Não persiste.** |
|
|
304
|
+
| Consumes | `text/plain` (CSV no corpo) |
|
|
305
|
+
| Query | `delimiter` (`comma`/`semicolon`/`tab`), `header` (`true`/`false`), `rows` (limite de linhas; default −1 = todas) |
|
|
306
|
+
| Parser | `readRows` (ingênuo, normaliza nomes de cabeçalho) |
|
|
307
|
+
| Resposta | `201` com a lista de linhas (campos `field1..N` se sem cabeçalho) |
|
|
308
|
+
|
|
309
|
+
### `POST /v3/csv/import`
|
|
310
|
+
| Aspecto | Detalhe |
|
|
311
|
+
|---|---|
|
|
312
|
+
| Finalidade | Importa linhas como `action_log` segundo o config |
|
|
313
|
+
| Consumes | `text/plain` |
|
|
314
|
+
| Query | `config` (= `_id` em `csv_config__c`), `async` (**aceito e ignorado**) |
|
|
315
|
+
| Comportamento | Sempre síncrono; só persiste se `config.action` estiver setado; descriptografia **não** se aplica aqui |
|
|
316
|
+
| Resposta | `201` com `{ action, log, all:[], err:[] }` |
|
|
317
|
+
|
|
318
|
+
### `POST /v3/csv/format`
|
|
319
|
+
| Aspecto | Detalhe |
|
|
320
|
+
|---|---|
|
|
321
|
+
| Finalidade | Salva um objeto de formato — **grava em `csv_format__c`** (não em `csv_config__c`) |
|
|
322
|
+
| Consumes | `application/json` |
|
|
323
|
+
| Comportamento | Gera `_id` se ausente; salva como BSON tipado. **Coleção órfã** — nenhum fluxo de leitura a consulta (ver §7) |
|
|
324
|
+
|
|
325
|
+
### `POST /v3/csv/{collection}/aggregate`
|
|
326
|
+
| Aspecto | Detalhe |
|
|
327
|
+
|---|---|
|
|
328
|
+
| Finalidade | Exporta o resultado de um pipeline de aggregation como CSV |
|
|
329
|
+
| Consumes | `application/json` (lista de stages do pipeline) |
|
|
330
|
+
| Query | `format` (config para `fields[]`), `delimiter` |
|
|
331
|
+
| Header | `Range` (paginação; default page 100) |
|
|
332
|
+
| Resposta | `text/plain` (CSV) + header `Content-Range` |
|
|
333
|
+
|
|
334
|
+
### `POST /v3/csv/{collection}/aggregate/to_excel`
|
|
335
|
+
| Aspecto | Detalhe |
|
|
336
|
+
|---|---|
|
|
337
|
+
| Finalidade | Igual ao acima, mas devolve planilha `.xls` (Apache POI) |
|
|
338
|
+
| Query | `format`, `name` (nome da planilha; default = nome da coleção) |
|
|
339
|
+
| Comportamento | Delimitador **forçado** para vírgula; arquivo temporário gerado |
|
|
340
|
+
| Resposta | `application/octet-stream` + `Content-Disposition: attachment; filename=<name>.xls` |
|
|
341
|
+
|
|
342
|
+
### `POST /v3/csv/split` / `POST /v3/csv/split/file`
|
|
343
|
+
| Aspecto | Detalhe |
|
|
344
|
+
|---|---|
|
|
345
|
+
| Finalidade | Quebra o CSV em arrays de colunas (preview estruturado). Não persiste. |
|
|
346
|
+
| Consumes | `text/plain` / `multipart/form-data` (parte `file`) |
|
|
347
|
+
| Query | `format`, `delimiter`, `total` (linhas a processar), `encoding` (só na variante file) |
|
|
348
|
+
| Comportamento | Usa `splitCsv` (Commons CSV); a variante `file` descriptografa se `upload_encrypted` |
|
|
349
|
+
| Resposta | `201` com lista de linhas (primeira linha = cabeçalho) |
|
|
350
|
+
|
|
351
|
+
### `POST /v3/csv/{collection}/bulk` / `POST /v3/csv/{collection}/bulk/file`
|
|
352
|
+
| Aspecto | Detalhe |
|
|
353
|
+
|---|---|
|
|
354
|
+
| Finalidade | Importa o CSV como documentos na coleção `{collection}` |
|
|
355
|
+
| Consumes | `text/plain` / `multipart/form-data` (parte `file`) |
|
|
356
|
+
| Query | `format` (config para `fields[]`), `delimiter`, `async`, `encoding` (só file) |
|
|
357
|
+
| Padrão | **Assíncrono** (somente `async=false` é síncrono) |
|
|
358
|
+
| Resposta | `201` com `{ config, log }` — use `log` para acompanhar via `/log/{log}` |
|
|
359
|
+
|
|
360
|
+
### `POST /v3/csv/{collection}/merge` / `POST /v3/csv/{collection}/merge/file`
|
|
361
|
+
| Aspecto | Detalhe |
|
|
362
|
+
|---|---|
|
|
363
|
+
| Finalidade | Igual ao bulk, porém aplica campos constantes (`upsert`) a **todas** as linhas |
|
|
364
|
+
| Consumes | `application/json` (`{ source, upsert }`) / `multipart/form-data` (parte `file` + parte `upsert`) |
|
|
365
|
+
| Query | `format`, `delimiter`, `async`, `encoding` (só file) |
|
|
366
|
+
| Comportamento | "merge" **não** é upsert de Mongo — ver §5. Na variante file, ausência da parte `upsert` → `400` |
|
|
367
|
+
| Resposta | `201` com `{ config, log }` |
|
|
368
|
+
|
|
369
|
+
### `GET /v3/csv/log/{log}`
|
|
370
|
+
| Aspecto | Detalhe |
|
|
371
|
+
|---|---|
|
|
372
|
+
| Finalidade | Consulta o status/progresso de uma importação |
|
|
373
|
+
| Resposta | `200` com o documento `CsvLog` (`status`, `percent`, `rows`, `start`, `finish`) |
|
|
374
|
+
|
|
375
|
+
**Exemplo — `/import` (request/response):**
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
POST /v3/csv/import?config=import_players
|
|
379
|
+
Content-Type: text/plain
|
|
380
|
+
|
|
381
|
+
userId;points;time
|
|
382
|
+
user01;100;2026-05-20 10:00:00
|
|
383
|
+
user02;250;2026-05-20 10:05:00
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{
|
|
388
|
+
"action": { "_id": "import_action", "...": "..." },
|
|
389
|
+
"log": { "_id": "1716200000abc", "config": "import_players",
|
|
390
|
+
"status": "finished", "rows": 2, "collection": "action_log" },
|
|
391
|
+
"all": [],
|
|
392
|
+
"err": []
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Exemplo — `/{collection}/bulk` (assíncrono):**
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
POST /v3/csv/player/bulk?format=player_format&delimiter=semicolon
|
|
400
|
+
|
|
401
|
+
_id;name
|
|
402
|
+
user01;Maria
|
|
403
|
+
user02;João
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{ "config": "player_format", "log": "1716200111xyz" }
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## 5. Regras de Negócio
|
|
413
|
+
|
|
414
|
+
Regras presentes no código mas ausentes do schema:
|
|
415
|
+
|
|
416
|
+
- **`match` é de→para invertido:** a **chave** do `match` é o campo destino do `ActionLog`; o **valor** é o nome da coluna CSV (`config.match.get(key)` → `row.get(valor)`). Errar essa ordem é a armadilha de configuração nº 1.
|
|
417
|
+
- **Campos especiais do ActionLog:** `time`, `userId` e `_id` são **extraídos** de `match` para os campos próprios do `ActionLog` e **removidos** de `attributes`. `time` só é aceito se for `Date`; `userId`/`_id` só se forem `String`.
|
|
418
|
+
- **Tipagem só com `action`:** a conversão de coluna para `Double` no import só ocorre quando há uma `action` e o atributo correspondente é do tipo `"Number"`. Sem action, tudo entra como string.
|
|
419
|
+
- **`/import` só persiste com `action`:** sem `config.action`, nenhuma linha é gravada (vira leitura + script).
|
|
420
|
+
- **"merge" = campos constantes, não upsert:** o objeto `upsert` é achatado em pares path/valor e aplicado a **cada** linha antes do `save`. Não há `$set`/upsert no Mongo — é `collection.save(object)` (substitui por `_id`, ou insere com `_id` novo).
|
|
421
|
+
- **Validação de `upsert` assimétrica:** só a variante multipart (`/merge/file`) valida a presença de `upsert` e retorna `400` se faltar. Na variante JSON (`/merge`), `upsert` ausente não é validado no controller — falha mais tarde dentro de `insertBulkSync`.
|
|
422
|
+
- **`collection.length > 3`:** bulk e aggregate ignoram silenciosamente coleções cujo nome tenha ≤ 3 caracteres (guard `collection.trim().length() > 3`).
|
|
423
|
+
- **Lote fixo de 100:** bulk processa 100 linhas por lote, re-prependendo o cabeçalho a cada lote. O comentário do código menciona "1000" — está desatualizado.
|
|
424
|
+
- **`header` ignorado em bulk:** o parser de bulk (`splitCsv`) sempre trata a primeira linha como cabeçalho, independentemente do config.
|
|
425
|
+
- **Injeção de `log_field`:** com `config.log` ligado e `log_field` definido, o `_id` do log é injetado em todas as linhas (no import via `row`, no bulk via `merge`).
|
|
426
|
+
- **Isolamento multi-tenant:** todo acesso usa `manager.getJongoConnection()`/`manager.getApiKey()` resolvido a partir do `apiKey` do token — a separação é por conexão de gamificação.
|
|
427
|
+
- **Consistência eventual:** imports assíncronos retornam `201` imediatamente; o resultado real só existe quando `csv_log__c.status == finished`.
|
|
428
|
+
|
|
429
|
+
---
|
|
430
|
+
|
|
431
|
+
## 6. Comportamentos Automáticos
|
|
432
|
+
|
|
433
|
+
| Comportamento | Trigger | Impacto | Persistência |
|
|
434
|
+
|---|---|---|---|
|
|
435
|
+
| Trigger `csv_before_bulk` | Início de `insertBulkSync` (bulk/merge) | Executa código de trigger custom com `log` no contexto | Depende do trigger |
|
|
436
|
+
| Trigger `csv_after_bulk` | Fim de `insertBulkSync` **(só em sucesso)** | Idem; **não dispara** se cancelado | Depende do trigger |
|
|
437
|
+
| Trigger `before_create`/`after_create` | Cada `save` em coleção ≠ `action_log` | Hooks por documento | Depende do trigger |
|
|
438
|
+
| Rastreio de `action_log` | Linha cujo destino é `action_log` (bulk) ou `/import` | `ActionManager.track`/`trackWithRestrictions` — dispara toda a engine de gamificação (achievements, níveis, etc.) | Sim |
|
|
439
|
+
| Geração de `_id` | Documento sem `_id` no bulk | `Guid.newShortGuid()` | Sim |
|
|
440
|
+
| Injeção de `log_field` | `config.log` + `log_field` | Adiciona o id do log a cada linha | Sim |
|
|
441
|
+
| Valores `default` | Coluna ausente/`null` com `default` no `fields[]` | Preenche o campo com o default tipado | Sim |
|
|
442
|
+
| Coerção de tipo | `fields[].type` ∈ {integer,double,boolean,date} | Converte a string da coluna | Sim |
|
|
443
|
+
| Descriptografia PGP | Upload multipart + `upload_encrypted=true` | `decryptTransfer` antes do parse | Não (em memória) |
|
|
444
|
+
|
|
445
|
+
> Os nomes `csv_before_bulk`/`csv_after_bulk` são **literais de string** passados a `TriggerManager.execute` (não constantes em `Trigger.java`). `before_create`/`after_create` são `Trigger.EVENT_BEFORE_CREATE`/`EVENT_AFTER_CREATE`.
|
|
446
|
+
|
|
447
|
+
---
|
|
448
|
+
|
|
449
|
+
## 7. Suportado vs NÃO Suportado
|
|
450
|
+
|
|
451
|
+
### ✅ Suportado
|
|
452
|
+
|
|
453
|
+
- Import como `action_log` orientado por config (`match`, `action`, `script` Groovy, `log`).
|
|
454
|
+
- Bulk/merge em **qualquer** coleção, síncrono ou assíncrono, em lotes, com cancelamento.
|
|
455
|
+
- Exportação de aggregation para CSV e Excel (`.xls`), com paginação por `Range`.
|
|
456
|
+
- Parsing/preview (`/read`, `/split`, `/split/file`).
|
|
457
|
+
- Upload multipart com descriptografia PGP (`upload_encrypted` + `CryptTransferKey`).
|
|
458
|
+
- Tipagem de campos (`integer`/`double`/`boolean`/`date`), valores default e campos aninhados/listas (`a.b`, `lista[0].x`).
|
|
459
|
+
- Acompanhamento de progresso via `csv_log__c` e `GET /log/{log}`.
|
|
460
|
+
- Cancelamento de import (alterando `csv_log__c.status` para `canceled`).
|
|
461
|
+
|
|
462
|
+
### ❌ NÃO Suportado / Inconsistências confirmadas no código
|
|
463
|
+
|
|
464
|
+
- **FTP** — não existe nenhum código de FTP no módulo (alegação da doc anterior é falsa).
|
|
465
|
+
- **`POST /v3/csv/format` é um beco sem saída** — grava em `csv_format__c`, mas todo fluxo de leitura (`find`, `getFormats`, `isEncrypted`) usa `csv_config__c`. O que é salvo ali nunca é lido.
|
|
466
|
+
- **Query param `async` em `/import`** — aceito e ignorado; o caminho assíncrono está comentado.
|
|
467
|
+
- **`pipe` em `/read` e `/import`** — `readRows` só conhece `comma`/`semicolon`/`tab`.
|
|
468
|
+
- **Aspas/escape em `/read` e `/import`** — `line.split` não trata aspas; valores com delimitador embutido quebram.
|
|
469
|
+
- **`header` em bulk/merge** — ignorado; a primeira linha é sempre cabeçalho.
|
|
470
|
+
- **Descriptografia em `/import`, `/read`, `/split`(texto), `/bulk`(texto), `/merge`(json)** — só os endpoints **multipart** chamam `getCsv`/`decryptTransfer`.
|
|
471
|
+
- **Endpoint de CRUD de config** — inexistente neste módulo; configs são criados/atualizados pela API genérica de banco (ex.: `POST /v3/database/csv_config__c`).
|
|
472
|
+
- **Endpoint de cancelamento** — inexistente; cancela-se via update direto em `csv_log__c`.
|
|
473
|
+
- **Status `running`** — comentado no código; nunca é gravado.
|
|
474
|
+
- **Campos `title` e `sample` do `CsvConfig`** — declarados, nunca lidos em runtime.
|
|
475
|
+
- **`merge` como upsert do Mongo** — não é; é aplicação de campos constantes + `save`.
|
|
476
|
+
- **Erros de `save` em bulk** — apenas `printStackTrace`, **não** refletidos no `CsvLog` nem na resposta.
|
|
477
|
+
- **`converCsvInObjectsOld` / segundo `convertObjectFieldsToSampleCsv`** — código legado morto/comentado, mantido no arquivo.
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## 8. Segurança e Permissões
|
|
482
|
+
|
|
483
|
+
- **Autenticação:** Bearer token via `AuthBean`; resolução de tenant por `apiKey` em `FrontController.getInstance(apiKey)`. **Sem** checagem de papel/escopo no `CsvRest`.
|
|
484
|
+
- **Isolamento multi-tenant:** por conexão Jongo da gamificação (`manager.getJongoConnection()`); chaves de criptografia filtradas por `gamification == apiKey`.
|
|
485
|
+
- **Superfície de leitura arbitrária:** `/{collection}/aggregate` e `/aggregate/to_excel` executam um **pipeline de aggregation arbitrário** (corpo da requisição) sobre **qualquer** coleção (`collection.length > 3`). Um chamador autenticado pode ler qualquer coleção da gamificação, inclusive `$lookup` entre coleções. Não há allowlist de coleções nem de stages.
|
|
486
|
+
- **Superfície de escrita arbitrária:** `/{collection}/bulk` e `/merge` gravam em **qualquer** coleção nomeada na URL, disparando `before_create`/`after_create`. Não há restrição de coleções destino.
|
|
487
|
+
- **Execução de código (Groovy):** `/import` com `config.script` executa Groovy. Há sandbox (`SecureASTCustomizer` com whitelist de tokens + `TriggerExpressionChecker` + `@TimedInterrupt 5s`), mas o script recebe `manager` (ManagerFactory completo) e `database` (GameDao) — superfície ampla. A criação do config (que carrega o script) deve ser tratada como operação privilegiada.
|
|
488
|
+
- **Criptografia em trânsito:** PGP assimétrico via `CryptTransferKey` (par publicKey/secretKey + passphrase, status `ACTIVE`). **Falha de descriptografia retorna `null`** → NPE no parser subsequente (falha silenciosa do ponto de vista do log).
|
|
489
|
+
- **Concorrência:** a fila assíncrona é única por instância e drenada por uma única thread — sem isolamento entre jobs além da serialização.
|
|
490
|
+
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## 9. Observabilidade e Troubleshooting
|
|
494
|
+
|
|
495
|
+
**Diagnóstico — o módulo está funcionando?**
|
|
496
|
+
|
|
497
|
+
```
|
|
498
|
+
# status/progresso de uma importação
|
|
499
|
+
GET /v3/csv/log/{log}
|
|
500
|
+
|
|
501
|
+
# config existe e está completo?
|
|
502
|
+
GET /v3/database/csv_config__c/{id} # via API genérica de banco
|
|
503
|
+
|
|
504
|
+
# os documentos chegaram na coleção destino?
|
|
505
|
+
GET /v3/database/{collection}?q={...}
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Queries MongoDB úteis:**
|
|
509
|
+
|
|
510
|
+
```js
|
|
511
|
+
// imports presos em "started" (travados/cancelados sem finalizar)
|
|
512
|
+
db.csv_log__c.find({ status: "started" })
|
|
513
|
+
|
|
514
|
+
// últimos logs de uma coleção
|
|
515
|
+
db.csv_log__c.find({ collection: "player" }).sort({ start: -1 })
|
|
516
|
+
|
|
517
|
+
// cancelar um import assíncrono em andamento
|
|
518
|
+
db.csv_log__c.updateOne({ _id: "<log>" }, { $set: { status: "canceled" } })
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**Erros comuns e causas:**
|
|
522
|
+
|
|
523
|
+
| Sintoma | Causa provável |
|
|
524
|
+
|---|---|
|
|
525
|
+
| `/import` retorna `log` mas nada é importado | `config.action` ausente — `/import` só grava `action_log` |
|
|
526
|
+
| Linhas faltando após import | Parser ingênuo descartou linhas com menos colunas que o cabeçalho; ou valores com delimitador embutido sem suporte a aspas |
|
|
527
|
+
| Colunas trocadas no import | `match` invertido (chave = destino, valor = coluna CSV) |
|
|
528
|
+
| `pipe` não funciona em `/import` | `readRows` não suporta pipe; use bulk |
|
|
529
|
+
| Datas viram string | `fields[].type` não é `date`, ou o `format` não bate com o valor |
|
|
530
|
+
| Upload criptografado falha sem erro claro | Endpoint não-multipart (não descriptografa) ou `CryptTransferKey` ativo ausente → `null` → NPE |
|
|
531
|
+
| Bulk "concluiu" mas faltam docs | Erros de `save` só vão para `printStackTrace`, não para o `CsvLog`; verifique os logs do servidor |
|
|
532
|
+
| Formato salvo via `/format` não tem efeito | Vai para `csv_format__c` (órfã); use `csv_config__c` |
|
|
533
|
+
| `percent` nunca chega a valores intermediários precisos | Divisão inteira trunca; só `100.0` é exato no fim |
|
|
534
|
+
|
|
535
|
+
**O que verificar quando algo não funciona:**
|
|
536
|
+
|
|
537
|
+
1. O `csv_config__c[id]` existe e tem `fields[]`/`match` corretos?
|
|
538
|
+
2. O delimitador do arquivo bate com o `delimiter` enviado?
|
|
539
|
+
3. Para bulk: a coleção destino existe e tem um doc amostra (afeta `reverseFormats`)?
|
|
540
|
+
4. Para criptografia: existe `CryptTransferKey` com `status=ACTIVE` para a gamificação?
|
|
541
|
+
5. Logs do servidor (`stdout`) — o módulo loga via `System.out.println` e `printStackTrace`.
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## 10. Exemplos Práticos
|
|
546
|
+
|
|
547
|
+
### Exemplo mínimo funcional — bulk de times (semicolon)
|
|
548
|
+
|
|
549
|
+
Config `csv_config__c`:
|
|
550
|
+
|
|
551
|
+
```json
|
|
552
|
+
{
|
|
553
|
+
"_id": "team_format",
|
|
554
|
+
"fields": [
|
|
555
|
+
{ "path": "_id", "name": "id" },
|
|
556
|
+
{ "path": "name", "name": "nome", "type": "string" }
|
|
557
|
+
]
|
|
558
|
+
}
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Requisição:
|
|
562
|
+
|
|
563
|
+
```
|
|
564
|
+
POST /v3/csv/team/bulk?format=team_format&delimiter=semicolon&async=false
|
|
565
|
+
|
|
566
|
+
id;nome
|
|
567
|
+
rh;Recursos Humanos
|
|
568
|
+
com;Equipe Comercial
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
→ cria dois documentos em `team` (`_id` = `rh`/`com`), síncrono.
|
|
572
|
+
|
|
573
|
+
### Exemplo avançado — import de action_log com tipagem, script e log
|
|
574
|
+
|
|
575
|
+
Config `csv_config__c`:
|
|
576
|
+
|
|
577
|
+
```json
|
|
578
|
+
{
|
|
579
|
+
"_id": "import_sales",
|
|
580
|
+
"delimiter": "semicolon",
|
|
581
|
+
"header": "true",
|
|
582
|
+
"action": "registrar_venda",
|
|
583
|
+
"match": {
|
|
584
|
+
"userId": "vendedor",
|
|
585
|
+
"time": "data",
|
|
586
|
+
"valor": "total"
|
|
587
|
+
},
|
|
588
|
+
"log": true,
|
|
589
|
+
"log_field": "import_ref",
|
|
590
|
+
"script": "void prepare(config, row, match){ if(match.valor == null){ match.valor = 0 } }"
|
|
591
|
+
}
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
Requisição:
|
|
595
|
+
|
|
596
|
+
```
|
|
597
|
+
POST /v3/csv/import?config=import_sales
|
|
598
|
+
Content-Type: text/plain
|
|
599
|
+
|
|
600
|
+
vendedor;data;total
|
|
601
|
+
user01;2026-05-20 10:00:00;1500.50
|
|
602
|
+
user02;2026-05-20 11:00:00;980.00
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
→ cada linha vira um `ActionLog` da action `registrar_venda`, com `userId`/`time` extraídos, `valor` no `attributes`, `import_ref` = id do log, e o script garante `valor` não-nulo.
|
|
606
|
+
|
|
607
|
+
### Exemplo de exportação — relatório CSV de pontos por jogador
|
|
608
|
+
|
|
609
|
+
```
|
|
610
|
+
POST /v3/csv/achievement/aggregate?delimiter=comma
|
|
611
|
+
Content-Type: application/json
|
|
612
|
+
Range: items=0-999
|
|
613
|
+
|
|
614
|
+
[
|
|
615
|
+
{ "$match": { "type": 0 } },
|
|
616
|
+
{ "$group": { "_id": "$player", "total": { "$sum": "$total" } } },
|
|
617
|
+
{ "$sort": { "total": -1 } }
|
|
618
|
+
]
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
→ devolve `text/plain` CSV com cabeçalho `_id,total`.
|
|
622
|
+
|
|
623
|
+
### Anti-pattern — o que NÃO fazer
|
|
624
|
+
|
|
625
|
+
```jsonc
|
|
626
|
+
// ERRADO: match com chave/valor invertidos
|
|
627
|
+
{ "match": { "vendedor": "userId" } } // grava o literal "userId" no campo "vendedor"
|
|
628
|
+
|
|
629
|
+
// ERRADO: confiar em async para ter o resultado na resposta
|
|
630
|
+
POST /v3/csv/player/bulk?format=f // 201 imediato; dados ainda não existem
|
|
631
|
+
|
|
632
|
+
// ERRADO: usar /format esperando configurar o import
|
|
633
|
+
POST /v3/csv/format // vai para csv_format__c, nunca é lido
|
|
634
|
+
|
|
635
|
+
// ERRADO: CSV com vírgula dentro do valor em /import
|
|
636
|
+
// "Silva, João" quebra (sem suporte a aspas no parser ingênuo)
|
|
637
|
+
// → use /bulk (Apache Commons CSV) ou troque o delimitador
|
|
638
|
+
```
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## Checklist de Configuração
|
|
643
|
+
|
|
644
|
+
- [ ] `csv_config__c[id]` criado via API de banco (não existe endpoint dedicado).
|
|
645
|
+
- [ ] `match` no sentido correto: **chave = campo destino**, **valor = coluna CSV**.
|
|
646
|
+
- [ ] Para `/import`: `action` definida (sem ela, nada é gravado).
|
|
647
|
+
- [ ] `delimiter` da requisição = delimitador real do arquivo.
|
|
648
|
+
- [ ] `pipe` apenas em bulk/merge/split — nunca em `/read`/`/import`.
|
|
649
|
+
- [ ] `fields[].type`/`format` corretos para datas e números (bulk/export).
|
|
650
|
+
- [ ] Para bulk síncrono: passar `async=false` explicitamente (o padrão é assíncrono).
|
|
651
|
+
- [ ] Para upload criptografado: usar endpoint **multipart** + `upload_encrypted=true` + `CryptTransferKey` ativo.
|
|
652
|
+
- [ ] Acompanhar imports assíncronos por `GET /log/{log}` até `status=finished`.
|
|
653
|
+
- [ ] Armadilha: erros de `save` em bulk **não** aparecem no `CsvLog` — conferir logs do servidor.
|
|
654
|
+
- [ ] Armadilha: `header` é ignorado em bulk (1ª linha sempre é cabeçalho).
|
|
655
|
+
- [ ] Armadilha: `merge` aplica campos constantes a todas as linhas — não é upsert de Mongo.
|