1553 lines
No EOL
38 KiB
Markdown
1553 lines
No EOL
38 KiB
Markdown
Objetivo
|
|
|
|
Transformar o ConvertX em um serviço backend interno, sem necessidade de UI, para suportar principalmente:
|
|
|
|
- DOCX -> TXT
|
|
- DOCX -> texto bruto JSON
|
|
- PDF -> texto
|
|
- XLSX -> texto/CSV
|
|
- ODT -> texto
|
|
- HTML/MD -> texto
|
|
- PPT -> TXT
|
|
- PPTX -> TXT
|
|
- DOC -> TXT
|
|
- XLS -> TXT
|
|
|
|
O uso principal será:
|
|
- n8n baixa arquivo do Nextcloud
|
|
- n8n envia arquivo ao serviço
|
|
- serviço devolve texto limpo
|
|
- n8n indexa no Supabase
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Decisão arquitetural
|
|
|
|
Não fazer
|
|
|
|
- scraping da UI do ConvertX
|
|
- automação de login/session/cookies
|
|
- usar endpoints web atuais como API informal
|
|
- expor interface pública para clientes
|
|
|
|
Fazer
|
|
|
|
Criar um fork com API interna dedicada, separada da UI.
|
|
|
|
### Estratégia recomendada
|
|
|
|
Adicionar uma camada nova de rotas, por exemplo:
|
|
|
|
- POST /api/extract-text
|
|
- POST /api/convert
|
|
- GET /healthz
|
|
|
|
Com autenticação simples por token interno.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Estado atual observado
|
|
|
|
Pelo container investigado:
|
|
|
|
- o projeto já tem motores funcionais:
|
|
- LibreOffice
|
|
- calibre
|
|
- pandoc
|
|
- markitdown
|
|
- o projeto já faz conversões reais
|
|
- logs confirmam conversão bem-sucedida de docx -> txt
|
|
- há código compilado indicando:
|
|
- src/converters/main
|
|
- handleConvert(...)
|
|
- conversores separados por engine
|
|
- a aplicação atual é UI-first
|
|
- existem rotas web, mas não uma API limpa/documentada para integração M2M
|
|
|
|
Conclusão:
|
|
a parte difícil da conversão já existe.
|
|
O trabalho principal é expor isso de forma limpa, previsível e segura.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Meta funcional do fork
|
|
|
|
MVP
|
|
|
|
Entregar um serviço que aceite upload de arquivo e retorne texto extraído.
|
|
|
|
### Endpoint principal
|
|
|
|
POST /api/extract-text
|
|
|
|
### Input
|
|
|
|
Multipart upload:
|
|
- file: arquivo
|
|
- opcional:
|
|
- filename
|
|
- mimeType
|
|
- preferredConverter
|
|
- formatHint
|
|
|
|
### Output
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"filename": "contrato.docx",
|
|
"detectedInputType": "docx",
|
|
"converter": "calibre",
|
|
"text": "texto extraído...",
|
|
"meta": {
|
|
"chars": 12345,
|
|
"warnings": [],
|
|
"durationMs": 842
|
|
}
|
|
}
|
|
```
|
|
|
|
V2
|
|
|
|
Adicionar endpoint de conversão genérica:
|
|
|
|
POST /api/convert
|
|
|
|
Input:
|
|
- arquivo
|
|
- target=txt|pdf|html|md
|
|
|
|
Output:
|
|
- binário convertido
|
|
ou
|
|
- JSON com downloadUrl temporária
|
|
|
|
V3
|
|
|
|
Adicionar modos assíncronos para arquivos grandes:
|
|
- POST /api/jobs
|
|
- GET /api/jobs/:id
|
|
- GET /api/jobs/:id/result
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Proposta de design técnico
|
|
|
|
1. Criar modo “service-only”
|
|
|
|
Adicionar variável de ambiente:
|
|
|
|
- SERVICE_MODE=true
|
|
|
|
### Comportamento
|
|
|
|
Quando SERVICE_MODE=true:
|
|
- não montar UI
|
|
- não depender de login visual
|
|
- subir apenas:
|
|
- healthcheck
|
|
- rotas de API
|
|
- middlewares essenciais
|
|
|
|
### Benefícios
|
|
|
|
- superfície menor
|
|
- menos complexidade
|
|
- mais fácil de operar
|
|
- menos risco de endpoints web desnecessários
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
2. Criar namespace /api
|
|
|
|
Separar totalmente das rotas existentes de UI.
|
|
|
|
### Rotas sugeridas
|
|
|
|
- GET /healthz
|
|
- GET /api/healthz
|
|
- POST /api/extract-text
|
|
- POST /api/convert
|
|
- opcional:
|
|
- GET /api/formats
|
|
- GET /api/converters
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3. Criar camada de autenticação interna
|
|
|
|
Como é serviço de infraestrutura, não precisa auth de usuário final.
|
|
|
|
### Recomendado
|
|
|
|
Auth por header estático:
|
|
|
|
- Authorization: Bearer <SERVICE_TOKEN>
|
|
|
|
ou
|
|
- X-API-Key: <SERVICE_TOKEN>
|
|
|
|
### Variável
|
|
|
|
- SERVICE_TOKEN=...
|
|
|
|
### Regra
|
|
|
|
- se SERVICE_MODE=true, bloquear todas as rotas /api/* sem token válido
|
|
- exceção:
|
|
- /healthz
|
|
|
|
### Se rodar só em rede interna
|
|
|
|
Pode até começar sem auth em ambiente fechado, mas eu recomendo token desde o início.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Arquivos/áreas prováveis para alterar no fork
|
|
|
|
Baseado no que encontrei no container:
|
|
|
|
Áreas principais
|
|
|
|
- src/index.tsx
|
|
- src/converters/main.ts
|
|
- src/converters/*.ts
|
|
- src/helpers/*
|
|
- páginas/rotas web em src/pages/*
|
|
|
|
Novo código a criar
|
|
|
|
### Novo diretório sugerido
|
|
|
|
- src/api/
|
|
|
|
### Arquivos sugeridos
|
|
|
|
- src/api/auth.ts
|
|
- src/api/health.ts
|
|
- src/api/extractText.ts
|
|
- src/api/convert.ts
|
|
- src/api/utils/files.ts
|
|
- src/api/utils/text.ts
|
|
- src/api/utils/cleanup.ts
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Implementação recomendada por etapas
|
|
|
|
Etapa 1 — mapear a entrada do app atual
|
|
|
|
Objetivo: entender o mínimo necessário para chamar a conversão sem UI.
|
|
|
|
### Tarefa DEV
|
|
|
|
Inspecionar:
|
|
- como src/pages/upload.tsx salva upload
|
|
- como src/pages/convert.tsx chama handleConvert
|
|
- assinatura real de handleConvert(...)
|
|
- formato esperado de:
|
|
- upload dir
|
|
- output dir
|
|
- converter name
|
|
- target format
|
|
|
|
### Entregável
|
|
|
|
Documento curto:
|
|
- fluxo atual de upload → conversão → output
|
|
- dependências implícitas do sistema de jobs
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Etapa 2 — isolar a lógica reutilizável
|
|
|
|
Objetivo: desacoplar conversão da camada web.
|
|
|
|
### Se handleConvert estiver muito acoplado
|
|
|
|
Criar função nova, por exemplo:
|
|
|
|
```ts
|
|
convertSingleFile({
|
|
inputPath,
|
|
outputDir,
|
|
target,
|
|
preferredConverter,
|
|
options
|
|
})
|
|
```
|
|
|
|
Retornando algo como:
|
|
|
|
```ts
|
|
{
|
|
outputPath: string,
|
|
converterUsed: string,
|
|
target: string
|
|
}
|
|
```
|
|
|
|
### Ideal
|
|
|
|
Essa função deve:
|
|
- não depender de cookie
|
|
- não depender de user id
|
|
- não depender de job id da UI
|
|
- não depender de db/history
|
|
|
|
### Entregável
|
|
|
|
Uma função reutilizável de backend puro.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Etapa 3 — implementar POST /api/extract-text
|
|
|
|
Objetivo: MVP útil para n8n.
|
|
|
|
### Fluxo interno
|
|
|
|
1. receber multipart com arquivo
|
|
2. salvar em diretório temporário
|
|
3. detectar tipo:
|
|
- por extensão
|
|
- e/ou MIME
|
|
4. escolher rota de extração
|
|
|
|
### Regras sugeridas por tipo
|
|
|
|
- docx
|
|
- tentar calibre -> txt
|
|
- fallback libreoffice -> txt
|
|
- fallback markitdown se fizer sentido
|
|
- odt
|
|
- libreoffice
|
|
- pdf
|
|
- se houver extrator apropriado, usar
|
|
- senão deixar fora do MVP
|
|
- txt, md, csv
|
|
- leitura direta
|
|
- html
|
|
- converter para texto via sanitização
|
|
|
|
### Pós-processamento de texto
|
|
|
|
Aplicar normalização:
|
|
- trim
|
|
- normalizar CRLF/LF
|
|
- remover excesso de linhas vazias
|
|
- opcional:
|
|
- remover caracteres nulos
|
|
- normalizar unicode
|
|
- colapsar whitespace sem destruir parágrafos
|
|
|
|
### Output
|
|
|
|
Sempre JSON:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"text": "...",
|
|
"converter": "calibre",
|
|
"detectedInputType": "docx",
|
|
"meta": {
|
|
"chars": 1234,
|
|
"durationMs": 820,
|
|
"tempFilesCleaned": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Erros
|
|
|
|
Padrão:
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "UNSUPPORTED_INPUT",
|
|
"message": "Unsupported input type: xlsm"
|
|
}
|
|
}
|
|
```
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Etapa 4 — implementar escolha de conversor
|
|
|
|
Objetivo: não depender de um único backend.
|
|
|
|
### Ordem recomendada para DOCX->TXT
|
|
|
|
1. calibre
|
|
2. libreoffice
|
|
3. pandoc ou markitdown se útil
|
|
|
|
### Por quê
|
|
|
|
Você já viu nos logs que o ConvertX converteu DOCX→TXT usando calibre com sucesso.
|
|
|
|
### Interface sugerida
|
|
|
|
Criar um resolvedor:
|
|
|
|
```ts
|
|
resolveTextExtractor(inputType, preferredConverter?)
|
|
```
|
|
|
|
Retorna:
|
|
- função de conversão
|
|
- nome do conversor
|
|
|
|
### Benefício
|
|
|
|
No futuro, trocar backend sem mexer na API.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Etapa 5 — implementar storage temporário robusto
|
|
|
|
Objetivo: evitar vazamento de arquivos e sujeira em disco.
|
|
|
|
### Recomendação
|
|
|
|
Estrutura temporária por request:
|
|
- /tmp/convertx-service/<request-id>/input
|
|
- /tmp/convertx-service/<request-id>/output
|
|
|
|
### Regras
|
|
|
|
- gerar requestId UUID
|
|
- limpar sempre no finally
|
|
- logar falha de cleanup sem quebrar resposta principal
|
|
- adicionar TTL cleanup defensivo por cron se quiser
|
|
|
|
### Importante
|
|
|
|
Nunca confiar no nome do arquivo bruto do usuário para compor path.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Etapa 6 — health e observabilidade
|
|
|
|
### Endpoint
|
|
|
|
GET /healthz
|
|
|
|
Retorno:
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"service": "convertx-service",
|
|
"serviceMode": true,
|
|
"converters": {
|
|
"libreoffice": true,
|
|
"calibre": true,
|
|
"pandoc": true
|
|
}
|
|
}
|
|
```
|
|
|
|
### Também recomendado
|
|
|
|
GET /api/formats
|
|
Retorna:
|
|
- entradas suportadas para extração
|
|
- conversores disponíveis
|
|
|
|
### Logging
|
|
|
|
Log por request:
|
|
- requestId
|
|
- filename
|
|
- detected input type
|
|
- converter escolhido
|
|
- duration
|
|
- sucesso/falha
|
|
|
|
Sem logar conteúdo do documento.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Contrato recomendado para o n8n
|
|
|
|
Chamada HTTP Request
|
|
|
|
POST /api/extract-text
|
|
|
|
### Headers
|
|
|
|
- Authorization: Bearer <SERVICE_TOKEN>
|
|
|
|
### Body
|
|
|
|
Multipart:
|
|
- file: binário do Nextcloud
|
|
|
|
### Resposta esperada no n8n
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"text": "..."
|
|
}
|
|
```
|
|
|
|
### Integração no workflow
|
|
|
|
Branch DOCX fica:
|
|
|
|
1. Nextcloud download
|
|
2. HTTP Request -> convertx-service /api/extract-text
|
|
3. Set data = {{$json.text}}
|
|
4. Update Data RAG
|
|
5. Move para processados
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Segurança
|
|
|
|
Requisitos mínimos
|
|
|
|
- rodar em rede interna
|
|
- autenticação por token
|
|
- limite de tamanho de upload
|
|
- tipos aceitos explicitamente
|
|
- sanitização de nome de arquivo
|
|
- limpeza garantida de temporários
|
|
- timeout por conversão
|
|
|
|
Recomendado
|
|
|
|
- MAX_UPLOAD_MB
|
|
- REQUEST_TIMEOUT_MS
|
|
- ALLOWED_INPUT_TYPES=docx,txt,md ,pdf,...
|
|
|
|
Não fazer
|
|
|
|
- expor sem auth na internet
|
|
- aceitar qualquer formato indiscriminadamente
|
|
- armazenar documentos convertidos por padrão
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Performance e concorrência
|
|
|
|
MVP
|
|
|
|
Processamento síncrono por request está ok.
|
|
|
|
Limites recomendados
|
|
|
|
- 1 request = 1 temp dir
|
|
- limite de concorrência configurável
|
|
- timeouts por engine
|
|
|
|
### Variáveis sugeridas
|
|
|
|
- MAX_CONCURRENT_CONVERSIONS=2 ou 4
|
|
- REQUEST_TIMEOUT_MS=60000
|
|
|
|
Se ficar pesado depois
|
|
|
|
Migrar para fila/job assíncrono.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Plano de entrega para DEV
|
|
|
|
Sprint 1 — API mínima funcional
|
|
|
|
### Objetivo
|
|
|
|
Subir POST /api/extract-text para DOCX.
|
|
|
|
### Itens
|
|
|
|
- fork do projeto
|
|
- SERVICE_MODE=true
|
|
- GET /healthz
|
|
- POST /api/extract-text
|
|
- token auth
|
|
- temp storage
|
|
- DOCX -> TXT via calibre/libreoffice
|
|
- retorno JSON com texto
|
|
- logs básicos
|
|
|
|
### Critério de aceite
|
|
|
|
- enviar DOCX e receber texto limpo
|
|
- n8n consegue consumir
|
|
- sem UI necessária
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Sprint 2 — endurecimento
|
|
|
|
### Itens
|
|
|
|
- fallback entre conversores
|
|
- suporte a TXT/MD/HTML direto
|
|
- timeouts
|
|
- limites de upload
|
|
- limpeza robusta
|
|
- testes automatizados
|
|
- imagem Docker própria do fork
|
|
|
|
### Critério de aceite
|
|
|
|
- falha controlada
|
|
- zero lixo de temp após requests
|
|
- respostas padronizadas de erro
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Sprint 3 — extensões úteis
|
|
|
|
### Itens
|
|
|
|
- POST /api/convert
|
|
- GET /api/formats
|
|
- suporte a PDF/XLSX conforme prioridade
|
|
- métricas e logs melhores
|
|
- docs internas para integração com n8n
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Testes que DEV precisa cobrir
|
|
|
|
Testes funcionais
|
|
|
|
### DOCX simples
|
|
|
|
- texto corrido
|
|
- múltiplos parágrafos
|
|
- heading + lista
|
|
|
|
### DOCX médio
|
|
|
|
- tabela
|
|
- quebra de página
|
|
- caracteres acentuados
|
|
- unicode
|
|
|
|
### Casos de erro
|
|
|
|
- arquivo inválido
|
|
- ZIP corrompido
|
|
- extensão falsa
|
|
- arquivo muito grande
|
|
- timeout de conversão
|
|
|
|
Testes de contrato
|
|
|
|
- resposta JSON sempre válida
|
|
- ok=true/false
|
|
- text presente quando sucesso
|
|
- error.code presente quando falha
|
|
|
|
Testes de operação
|
|
|
|
- cleanup de temp
|
|
- concorrência de 2-5 requests
|
|
- comportamento com conversor ausente
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Docker/Deploy do fork
|
|
|
|
Recomendação
|
|
|
|
Imagem própria, por exemplo:
|
|
- ghcr.io/<org>/convertx-service
|
|
|
|
Execução
|
|
|
|
Sem UI e com modo serviço:
|
|
- SERVICE_MODE=true
|
|
- SERVICE_TOKEN=...
|
|
|
|
### Exemplo de compose conceitual
|
|
|
|
```yaml
|
|
services:
|
|
convertx-service:
|
|
image: ghcr.io/<org>/convertx-service:latest
|
|
restart: unless-stopped
|
|
ports:
|
|
- "127.0.0.1:3010:3000"
|
|
environment:
|
|
- SERVICE_MODE=true
|
|
- SERVICE_TOKEN=supersecret
|
|
- MAX_UPLOAD_MB=50
|
|
- REQUEST_TIMEOUT_MS=60000
|
|
volumes:
|
|
- ./data:/app/data
|
|
```
|
|
|
|
### Observação
|
|
|
|
Se não precisar persistência, pode usar volume temporário.
|
|
Mas algum diretório de trabalho é útil para debug inicial.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Riscos e mitigação
|
|
|
|
Risco 1 — handleConvert muito acoplado à UI/job/db
|
|
|
|
### Mitigação
|
|
|
|
Extrair função backend pura de conversão.
|
|
|
|
Risco 2 — output TXT ruim em alguns DOCX
|
|
|
|
### Mitigação
|
|
|
|
Implementar fallback entre calibre/libreoffice/markitdown.
|
|
|
|
Risco 3 — vazar arquivos temporários
|
|
|
|
### Mitigação
|
|
|
|
try/finally + cleanup periódico defensivo.
|
|
|
|
Risco 4 — serviço virar gargalo
|
|
|
|
### Mitigação
|
|
|
|
limite de concorrência + timeout + futura fila assíncrona.
|
|
|
|
Risco 5 — quebra com updates upstream
|
|
|
|
### Mitigação
|
|
|
|
manter mudanças isoladas em src/api/* e em poucos pontos do bootstrap.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Recomendação de abordagem de código
|
|
|
|
Evitar
|
|
|
|
Modificar agressivamente o fluxo web já existente.
|
|
|
|
Preferir
|
|
|
|
Adicionar uma camada nova, quase orthogonal:
|
|
- src/api/*
|
|
- boot condicional no src/index.tsx
|
|
|
|
Assim o fork:
|
|
- continua próximo do upstream
|
|
- fica mais fácil de manter
|
|
- reduz conflito de merge
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Definição de pronto
|
|
|
|
O fork estará “pronto” para uso no n8n quando:
|
|
|
|
- POST /api/extract-text aceitar DOCX
|
|
- devolver texto confiável
|
|
- exigir token interno
|
|
- limpar temporários
|
|
- expor GET /healthz
|
|
- rodar em container sem UI
|
|
- ser consumível pelo workflow com um único HTTP Request
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Minha recomendação final ao time DEV
|
|
|
|
Build order
|
|
|
|
1. extrair/consolidar função backend de conversão
|
|
2. criar /api/extract-text
|
|
3. adicionar auth + cleanup
|
|
4. dockerizar modo service-only
|
|
5. integrar no n8n
|
|
6. só depois pensar em /api/convert genérico
|
|
|
|
Porque o caso de uso real hoje é:
|
|
DOCX -> texto para RAG
|
|
e não um conversor universal completo.
|
|
|
|
Se quiser, no próximo passo eu posso transformar isso em um artefato mais operacional para o time, por exemplo:
|
|
- checklist técnico por tarefa
|
|
- ADR curta
|
|
- ou especificação de endpoints com payloads exatos.
|
|
|
|
|
|
faça os 3
|
|
|
|
|
|
Perfeito. Vou te entregar os 3 artefatos:
|
|
|
|
1. checklist técnico por tarefa
|
|
2. ADR curta
|
|
3. especificação de endpoints com payloads exatos
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
1) CHECKLIST TÉCNICO POR TAREFA
|
|
|
|
Projeto: Fork do ConvertX para modo service-only interno
|
|
|
|
Objetivo do MVP
|
|
|
|
Entregar um serviço interno que faça:
|
|
|
|
- DOCX -> texto
|
|
- consumível por n8n via HTTP
|
|
- sem UI
|
|
- sem login humano
|
|
- com token interno
|
|
- com cleanup de arquivos temporários
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC A — Preparação do fork
|
|
|
|
### Tarefa A1 — Criar fork e baseline
|
|
|
|
Checklist
|
|
- Criar fork do repositório C4illin/ConvertX
|
|
- Definir nome do fork/produto interno
|
|
- sugestão: convertx-service
|
|
- Criar branch inicial:
|
|
- feat/service-mode-api
|
|
- Documentar hash/tag upstream base usada no fork
|
|
- Confirmar build local do projeto original
|
|
- Confirmar que a imagem Docker do fork sobe igual ao upstream
|
|
|
|
Entregável
|
|
- fork funcional
|
|
- branch de trabalho criada
|
|
- README interno inicial
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa A2 — Mapear fluxo atual de conversão
|
|
|
|
Checklist
|
|
- Inspecionar src/index.tsx
|
|
- Inspecionar src/pages/upload*
|
|
- Inspecionar src/pages/convert*
|
|
- Inspecionar src/converters/main*
|
|
- Localizar assinatura real de handleConvert(...)
|
|
- Identificar dependências com:
|
|
- user/session
|
|
- jobId
|
|
- sqlite/history
|
|
- diretórios uploads/output
|
|
- Mapear onde ocorre a seleção de conversor
|
|
- Mapear onde o output final é salvo
|
|
- Mapear tratamento de erro atual
|
|
|
|
Entregável
|
|
- documento curto: “conversion flow map”
|
|
- diagrama simples do fluxo atual
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC B — Desacoplamento da lógica de conversão
|
|
|
|
### Tarefa B1 — Extrair função backend pura
|
|
|
|
Objetivo
|
|
Criar função reutilizável sem dependência da UI.
|
|
|
|
Checklist
|
|
- Criar módulo novo, ex:
|
|
- src/service/convertSingleFile.ts
|
|
- Definir interface de entrada:
|
|
```ts
|
|
type ConvertSingleFileInput = {
|
|
inputPath: string
|
|
outputDir: string
|
|
target: string
|
|
preferredConverter?: string
|
|
options?: Record<string, unknown>
|
|
}
|
|
```
|
|
|
|
- Definir interface de saída:
|
|
```ts
|
|
type ConvertSingleFileResult = {
|
|
outputPath: string
|
|
converterUsed: string
|
|
target: string
|
|
durationMs: number
|
|
}
|
|
```
|
|
|
|
- Reusar lógica de conversão existente
|
|
- Remover dependência de:
|
|
- cookie
|
|
- user id
|
|
- job id
|
|
- páginas web
|
|
- Garantir que a função lance erro padronizado em falha
|
|
|
|
Entregável
|
|
- função backend pura testável
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa B2 — Extrair estratégia de “texto”
|
|
|
|
Objetivo
|
|
Criar uma camada que escolha o melhor conversor para extrair texto.
|
|
|
|
Checklist
|
|
- Criar módulo:
|
|
- src/service/extractText.ts
|
|
- Implementar detecção de tipo por:
|
|
- extensão
|
|
- MIME opcional
|
|
- Criar resolver de conversor:
|
|
```ts
|
|
resolveTextExtractor(inputType, preferredConverter?)
|
|
```
|
|
|
|
- Ordem inicial para DOCX:
|
|
- calibre
|
|
- libreoffice
|
|
- markitdown/pandoc se aplicável
|
|
- Definir retorno:
|
|
```ts
|
|
type ExtractTextResult = {
|
|
text: string
|
|
detectedInputType: string
|
|
converterUsed: string
|
|
durationMs: number
|
|
warnings?: string[]
|
|
}
|
|
```
|
|
|
|
Entregável
|
|
- função extractTextFromFile(...)
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC C — Modo service-only
|
|
|
|
### Tarefa C1 — Criar flag de execução
|
|
|
|
Checklist
|
|
- Adicionar env var:
|
|
- SERVICE_MODE=true|false
|
|
- No bootstrap (src/index.tsx), separar:
|
|
- rotas web/UI
|
|
- rotas de serviço/API
|
|
- Quando SERVICE_MODE=true:
|
|
- não montar páginas UI
|
|
- não montar login/register/history/results
|
|
- montar apenas health + API
|
|
- Manter compatibilidade com modo original se necessário
|
|
|
|
Entregável
|
|
- app sobe em modo só-serviço
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa C2 — Criar autenticação por token
|
|
|
|
Checklist
|
|
- Adicionar env:
|
|
- SERVICE_TOKEN
|
|
- Criar middleware:
|
|
- src/api/auth.ts
|
|
- Ler header:
|
|
- Authorization: Bearer <token>
|
|
- Permitir /healthz sem auth
|
|
- Bloquear /api/* sem token válido
|
|
- Padronizar erro 401:
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "UNAUTHORIZED",
|
|
"message": "Invalid or missing service token"
|
|
}
|
|
}
|
|
```
|
|
|
|
Entregável
|
|
- autenticação M2M funcionando
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC D — API de extração
|
|
|
|
### Tarefa D1 — Healthcheck
|
|
|
|
Checklist
|
|
- Criar GET /healthz
|
|
- Criar GET /api/healthz opcional
|
|
- Retornar:
|
|
- status do serviço
|
|
- SERVICE_MODE
|
|
- disponibilidade de conversores
|
|
- Verificar presença real de:
|
|
- libreoffice
|
|
- calibre
|
|
- pandoc
|
|
- markitdown se existir
|
|
|
|
Entregável
|
|
- health confiável para monitoramento e n8n
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa D2 — Endpoint POST /api/extract-text
|
|
|
|
Checklist
|
|
- Criar rota:
|
|
- POST /api/extract-text
|
|
- Aceitar multipart form-data
|
|
- Campo obrigatório:
|
|
- file
|
|
- Campos opcionais:
|
|
- preferredConverter
|
|
- filename
|
|
- mimeType
|
|
- formatHint
|
|
- Validar tamanho do upload
|
|
- Persistir em diretório temporário
|
|
- Executar extração
|
|
- Ler arquivo convertido TXT se o backend converter para TXT
|
|
- Normalizar o texto
|
|
- Devolver JSON
|
|
|
|
Entregável
|
|
- endpoint consumível pelo n8n
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa D3 — Normalização de texto
|
|
|
|
Checklist
|
|
- Criar módulo:
|
|
- src/api/utils/text.ts
|
|
- Aplicar:
|
|
- trim
|
|
- normalização de newline
|
|
- remoção de NUL bytes
|
|
- colapso de whitespace horizontal excessivo
|
|
- preservação de parágrafos
|
|
- limite opcional de tamanho
|
|
- Adicionar warnings quando:
|
|
- texto vier vazio
|
|
- conversão tiver fallback
|
|
- arquivo parecer corrompido
|
|
|
|
Entregável
|
|
- texto mais estável para RAG
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC E — Arquivos temporários e robustez
|
|
|
|
### Tarefa E1 — Temp storage por request
|
|
|
|
Checklist
|
|
- Criar requestId UUID por request
|
|
- Estrutura:
|
|
- /tmp/convertx-service/<requestId>/input
|
|
- /tmp/convertx-service/<requestId>/output
|
|
- Sanitizar nome do arquivo
|
|
- Nunca confiar em path do usuário
|
|
- Sempre limpar em finally
|
|
- Logar falha de cleanup sem quebrar a resposta
|
|
|
|
Entregável
|
|
- zero sujeira estrutural por request
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa E2 — Timeouts e limites
|
|
|
|
Checklist
|
|
- Adicionar env:
|
|
- MAX_UPLOAD_MB
|
|
- REQUEST_TIMEOUT_MS
|
|
- MAX_CONCURRENT_CONVERSIONS
|
|
- Aplicar timeout por processo de conversão
|
|
- Aplicar limite de upload
|
|
- Rejeitar tipos não suportados
|
|
- Padronizar erro:
|
|
- PAYLOAD_TOO_LARGE
|
|
- UNSUPPORTED_INPUT
|
|
- CONVERSION_TIMEOUT
|
|
|
|
Entregável
|
|
- serviço operacionalmente previsível
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC F — Docker e operação
|
|
|
|
### Tarefa F1 — Dockerfile / imagem do fork
|
|
|
|
Checklist
|
|
- Reutilizar imagem base do projeto ou adaptar a atual
|
|
- Garantir que conversores necessários estejam presentes
|
|
- Garantir modo service-only no start
|
|
- Expor porta única
|
|
- Configurar env defaults seguros
|
|
|
|
Entregável
|
|
- imagem convertx-service
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
### Tarefa F2 — Compose de operação
|
|
|
|
Checklist
|
|
- Criar compose interno
|
|
- Bind local de porta
|
|
- ex: 127.0.0.1:3010:3000
|
|
- Definir volume de dados se necessário
|
|
- Definir restart policy
|
|
- Definir token por env
|
|
|
|
Entregável
|
|
- deploy repetível
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC G — Integração com n8n
|
|
|
|
### Tarefa G1 — Contrato n8n
|
|
|
|
Checklist
|
|
- Documentar chamada HTTP Request no n8n
|
|
- Exemplo com multipart binário
|
|
- Exemplo de headers
|
|
- Exemplo de resposta
|
|
- Exemplo de mapping:
|
|
- data = {{$json.text}}
|
|
|
|
Entregável
|
|
- handoff claro para o time de automação
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
EPIC H — Testes
|
|
|
|
### Tarefa H1 — Testes funcionais
|
|
|
|
Checklist
|
|
- DOCX simples
|
|
- DOCX com acentos
|
|
- DOCX com tabela
|
|
- DOCX com listas
|
|
- DOCX vazio
|
|
- arquivo inválido/corrompido
|
|
- extensão falsa
|
|
- timeout forçado
|
|
|
|
### Tarefa H2 — Testes operacionais
|
|
|
|
Checklist
|
|
- concorrência 2-5 requests
|
|
- cleanup após sucesso
|
|
- cleanup após falha
|
|
- auth inválida
|
|
- healthcheck sem auth
|
|
|
|
Entregável
|
|
- suíte mínima de confiança
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
2) ADR CURTA
|
|
|
|
ADR-001 — Fork do ConvertX como serviço interno de extração de texto
|
|
|
|
### Status
|
|
|
|
Proposto
|
|
|
|
### Contexto
|
|
|
|
O fluxo RAG atual depende de ingestão automática de arquivos do Nextcloud e precisa extrair texto de documentos, especialmente .docx, sem:
|
|
|
|
- autenticação Google
|
|
- UI humana
|
|
- automação frágil de browser/login
|
|
|
|
Foi validado que o ConvertX, rodando em container, já converte DOCX -> TXT com sucesso usando sua stack interna (ex.: calibre/LibreOffice). Porém, a aplicação
|
|
atual é orientada a UI e não expõe uma API simples/documentada para integração máquina-a-máquina.
|
|
|
|
### Decisão
|
|
|
|
Fazer um fork do ConvertX e adicionar um modo service-only, com rotas internas dedicadas para automação, iniciando com:
|
|
|
|
- GET /healthz
|
|
- POST /api/extract-text
|
|
|
|
O serviço será protegido por token interno e consumido pelo n8n.
|
|
|
|
### Racional
|
|
|
|
Essa abordagem:
|
|
- reutiliza a stack de conversão já validada
|
|
- evita desenvolver um conversor do zero
|
|
- evita depender de Google
|
|
- evita scraping da UI
|
|
- reduz confusão operacional com clientes
|
|
- mantém a extração como componente de infraestrutura isolado
|
|
|
|
### Alternativas consideradas
|
|
|
|
#### 1. Google como backend de conversão
|
|
|
|
Rejeitada por exigir autenticação e gerar complexidade externa indesejada.
|
|
|
|
#### 2. LibreOffice no host, chamado diretamente
|
|
|
|
Viável, mas mais chato de operar, versionar e encapsular do que reaproveitar o container já funcional.
|
|
|
|
#### 3. ConvertX UI com automação/scraping
|
|
|
|
Rejeitada por fragilidade e baixa mantenibilidade.
|
|
|
|
#### 4. Serviço local custom do zero
|
|
|
|
Viável, mas desnecessário dado que o ConvertX já resolve a parte difícil da conversão.
|
|
|
|
### Consequências positivas
|
|
|
|
- integração limpa com n8n
|
|
- backend interno sem UI
|
|
- melhor separação de responsabilidades
|
|
- facilidade de evolução para outros formatos
|
|
|
|
### Consequências negativas
|
|
|
|
- custo de manutenção do fork
|
|
- necessidade de isolar mudanças para facilitar merge com upstream
|
|
- necessidade de lidar com cleanup, auth e limites operacionais
|
|
|
|
### Decisão operacional
|
|
|
|
Implementar primeiro um MVP de DOCX -> texto via POST /api/extract-text, depois expandir se necessário.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3) ESPECIFICAÇÃO DE ENDPOINTS
|
|
|
|
Base URL
|
|
|
|
Interna, por exemplo:
|
|
- http://convertx-service:3000
|
|
ou
|
|
- http://127.0.0.1:3010
|
|
|
|
Autenticação
|
|
|
|
Header obrigatório para /api/*:
|
|
|
|
```http
|
|
Authorization: Bearer <SERVICE_TOKEN>
|
|
```
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3.1 GET /healthz
|
|
|
|
### Objetivo
|
|
|
|
Sonda simples para monitoramento e readiness.
|
|
|
|
### Auth
|
|
|
|
Não obrigatória
|
|
|
|
### Request
|
|
|
|
```http
|
|
GET /healthz
|
|
```
|
|
|
|
### Response 200
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"service": "convertx-service",
|
|
"serviceMode": true,
|
|
"version": "0.1.0",
|
|
"converters": {
|
|
"libreoffice": true,
|
|
"calibre": true,
|
|
"pandoc": true,
|
|
"markitdown": false
|
|
}
|
|
}
|
|
```
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3.2 GET /api/formats
|
|
|
|
### Objetivo
|
|
|
|
Listar formatos e conversores disponíveis.
|
|
|
|
### Auth
|
|
|
|
Obrigatória
|
|
|
|
### Request
|
|
|
|
```http
|
|
GET /api/formats
|
|
Authorization: Bearer <SERVICE_TOKEN>
|
|
```
|
|
|
|
### Response 200
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"extractText": {
|
|
"inputs": ["docx", "txt", "md", "html", "odt"],
|
|
"preferredConverters": ["calibre", "libreoffice", "pandoc", "markitdown"]
|
|
}
|
|
}
|
|
```
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3.3 POST /api/extract-text
|
|
|
|
### Objetivo
|
|
|
|
Receber um arquivo e retornar texto extraído.
|
|
|
|
### Auth
|
|
|
|
Obrigatória
|
|
|
|
### Content-Type
|
|
|
|
multipart/form-data
|
|
|
|
### Campos
|
|
|
|
#### Obrigatório
|
|
|
|
- file — binário do arquivo
|
|
|
|
#### Opcionais
|
|
|
|
- preferredConverter — string
|
|
- exemplos: calibre, libreoffice
|
|
- filename — string
|
|
- mimeType — string
|
|
- formatHint — string
|
|
- ex.: docx
|
|
|
|
### Exemplo cURL
|
|
|
|
```bash
|
|
curl -X POST http://127.0.0.1:3010/api/extract-text \
|
|
-H "Authorization: Bearer SEU_TOKEN" \
|
|
-F "file=@contrato.docx" \
|
|
-F "preferredConverter=calibre"
|
|
```
|
|
|
|
### Response 200
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"filename": "contrato.docx",
|
|
"detectedInputType": "docx",
|
|
"converter": "calibre",
|
|
"text": "Conteúdo extraído do documento...",
|
|
"meta": {
|
|
"chars": 18432,
|
|
"durationMs": 913,
|
|
"warnings": []
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response 400 — arquivo ausente
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "MISSING_FILE",
|
|
"message": "Field 'file' is required"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response 400 — tipo não suportado
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "UNSUPPORTED_INPUT",
|
|
"message": "Unsupported input type: xlsm"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response 401 — auth inválida
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "UNAUTHORIZED",
|
|
"message": "Invalid or missing service token"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response 413 — arquivo grande
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "PAYLOAD_TOO_LARGE",
|
|
"message": "File exceeds maximum allowed size"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Response 500 — falha de conversão
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"error": {
|
|
"code": "CONVERSION_FAILED",
|
|
"message": "Failed to extract text from file"
|
|
}
|
|
}
|
|
```
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3.4 POST /api/convert
|
|
|
|
### Objetivo
|
|
|
|
Conversão genérica de arquivo para formato alvo.
|
|
|
|
### Status
|
|
|
|
Opcional / pós-MVP
|
|
|
|
### Auth
|
|
|
|
Obrigatória
|
|
|
|
### Request
|
|
|
|
Multipart:
|
|
- file
|
|
- target
|
|
- preferredConverter opcional
|
|
|
|
### Exemplo
|
|
|
|
```bash
|
|
curl -X POST http://127.0.0.1:3010/api/convert \
|
|
-H "Authorization: Bearer SEU_TOKEN" \
|
|
-F "file=@arquivo.docx" \
|
|
-F "target=txt"
|
|
```
|
|
|
|
### Resposta possível
|
|
|
|
#### modo JSON
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"filename": "arquivo.txt",
|
|
"converter": "calibre",
|
|
"contentBase64": "SGVsbG8uLi4="
|
|
}
|
|
```
|
|
|
|
#### ou modo binário
|
|
|
|
retorno direto do arquivo convertido com:
|
|
- Content-Type
|
|
- Content-Disposition
|
|
|
|
### Recomendação
|
|
|
|
Para o caso RAG, não priorizar este endpoint no MVP.
|
|
Priorizar extract-text.
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
3.5 Especificação de integração com n8n
|
|
|
|
### Chamada
|
|
|
|
No HTTP Request node:
|
|
|
|
- Method: POST
|
|
- URL: http://convertx-service:3000/api/extract-text
|
|
- Auth header:
|
|
```text
|
|
Authorization: Bearer {{$env.CONVERTX_SERVICE_TOKEN}}
|
|
```
|
|
|
|
- Send Binary Data: true
|
|
- Binary Property: data
|
|
- Content type: multipart/form-data
|
|
|
|
### Resposta esperada
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"text": "..."
|
|
}
|
|
```
|
|
|
|
### Mapping seguinte
|
|
|
|
No Set:
|
|
|
|
```javascript
|
|
{{ $json.text }}
|
|
```
|
|
|
|
para o campo:
|
|
- data
|
|
|
|
────────────────────────────────────────────────────────────────────────────────
|
|
|
|
Recomendação final de execução
|
|
|
|
Ordem
|
|
|
|
1. MVP extract-text
|
|
2. health + auth + cleanup
|
|
3. integração n8n
|
|
4. testes DOCX reais
|
|
5. só depois:
|
|
- formats
|
|
- convert
|
|
- outros formatos |