NodeOtimizacaoDoProceso
NodeOtimizacaoDoProcesso 🎥🚀
Automação de Pipeline para Vídeos Educacionais
Este projeto é uma solução backend robusta desenvolvida em Node.js para automatizar completamente o ciclo de vida de gravações de aulas: desde a captura no Zoom, passando pelo processamento e upload no Vimeo, até a publicação final no Canvas LMS.
📖 O Problema & A Solução
O Problema: O processo manual de baixar gravações, converter, fazer upload e publicar em plataformas de ensino é lento, repetitivo e propenso a erros humanos.
A Solução: Uma API inteligente que orquestra todo esse fluxo em minutos, sem intervenção manual, garantindo padronização e agilidade na disponibilização de conteúdo para os alunos.
� Fluxo de Trabalho (Pipeline)
O sistema opera através de um pipeline de eventos coordenado:
Carregando diagrama...
✨ Funcionalidades Principais
🛠️ Stack Tecnológica
- Core: Node.js, Express.js
- Real-time: Socket.io
- Infra/Cache: Redis
- Integrações: Zoom API, Vimeo API, Canvas LMS API, Google Drive API
- Utils: Axios, Fluent-ffmpeg
📁 Arquitetura
Cada arquivo tem uma responsabilidade, e cada camada só conhece a de baixo.
Rota não conhece axios. Integração não conhece socket. Pipeline não conhece
Express. A regra é verificada automaticamente por npm run check:camadas.
src/
├── server.js # bootstrap: monta as peças e escuta
├── app.js # monta o Express (middlewares + rotas)
│
├── config/ # tudo que é configuração
│ ├── env.js # carrega e valida o .env
│ ├── paths.js # todos os caminhos do projeto
│ └── credenciais.js # .env + override em runtime via config.json
│
├── http/ # camada de entrada — só request e response
│ ├── middleware/ # auth, cors, rateLimiter, errorHandler
│ └── routes/ # valida a entrada, chama o pipeline, responde
│ ├── index.js # monta e protege as rotas
│ ├── auth.routes.js # POST /login
│ ├── zoom.routes.js # POST /zoom/:id
│ ├── canvas.routes.js # POST /canvas/course/:idCourse/:idZoom
│ ├── config.routes.js # /config
│ └── storage.routes.js # /temp
│
├── integrations/ # um cliente por serviço externo
│ ├── zoom.client.js # token, buscar gravação, baixar, apagar
│ ├── vimeo.client.js # criar, upload TUS, nome, pasta, capa, privacidade
│ ├── canvas.client.js # criar página
│ └── googleDrive.client.js # upload retomável
│
├── pipeline/ # regra de negócio, sem saber o que é HTTP
│ ├── processarGravacao.js # Zoom -> Vimeo -> Drive
│ └── publicarNoCanvas.js # Vimeo -> Canvas -> limpeza
│
├── storage/ # único dono do disco
│ ├── workspace.js # pasta de cada gravação, espaço, limpeza
│ └── referenciaVimeo.js # qual vídeo do Vimeo é de qual gravação
│
├── realtime/ # socket.io
│ ├── io.js # conexão, autenticação, registro de sockets
│ └── progresso.js # repórter de progresso injetado no pipeline
│
└── lib/ # utilidades sem dependência de camada
├── asyncHandler.js
└── logger.js
O que mudou, em números
| Antes | Depois | ||
|---|---|---|---|
App.js | 142 linhas | server.js + app.js | 60 + 38 |
Zoom.js | 144 linhas | zoom.routes.js | 64 |
Canvas.js | 213 linhas | canvas.routes.js | 47 |
Vimeo.js | 269 linhas | (virou vimeo.client.js + pipeline) |
O Zoom.js de 144 linhas continha o cliente OAuth do Zoom, a consulta da
gravação, a formatação da data, o download em stream e o encadeamento com o
Vimeo. O Canvas.js de 213 linhas tinha uma segunda cópia idêntica do
cliente OAuth do Zoom, mais a API do Canvas, a API do Vimeo, a montagem do HTML
e a remoção de arquivos do disco.
O userSocket que atravessava tudo
Antes, o objeto de WebSocket era parâmetro de praticamente toda função:
rota -> zoomMain -> baixarVideoZoom -> processarVideoParaVimeo
-> uploadVideoParaVimeo -> CreateFileGoogleDrive -> setVideoThumbnail
Sete níveis carregando um socket só para escrever uma linha de status. O cliente da API do Vimeo precisava conhecer socket.io, e o pipeline não conseguia rodar sem alguém conectado. Agora o pipeline recebe um repórter de progresso, e quem o constrói decide o destino — um socket, vários, ou nenhum.
💾 Ciclo de vida dos arquivos
O vídeo baixado é o recurso escasso: 1 a 3 GB por gravação, num disco de 60 GB. As regras:
O que era antes: o arquivo só saía do disco no fim do fluxo do Canvas — que é uma ação separada do usuário e podia nunca acontecer — ou pelo
DELETE /temp/videosmanual. Não haviafinally, varredura nem TTL.O nó que impedia a correção: a referência do Vimeo era um
.txtguardado dentro da pasta do vídeo, e o dado era o próprio nome do arquivo. Um dado de 8 bytes preso a um arquivo de 2 GB — não dava para apagar um sem perder o outro.
⬇️ Download do Zoom
Baixar 1 a 3 GB de uma VM é a etapa mais frágil do processo. As regras:
O que era antes:
recording_files[0], às cegas. Umaxios.getcom.pipe()onde só o erro de escrita era tratado — falha na origem deixava a Promise sem resolver. Nenhuma retentativa: qualquer soluço de rede jogava fora o download inteiro. E o token ia na URL, parando em log de proxy e no histórico de redirect.
⬆️ Upload para Vimeo e Drive
Sobre o paralelismo: se a banda de subida da VM for o gargalo, subir os dois ao mesmo tempo divide a banda e o ganho é pequeno. O ganho é grande quando o gargalo é latência ou processamento do outro lado, que é o caso típico de upload em partes. Por isso é configurável:
UPLOAD_PARALELO=falsevolta ao comportamento sequencial para você comparar.
Avaliado e descartado: transmitir do Zoom direto para o Vimeo sem passar pelo disco eliminaria a etapa de download do caminho crítico. Mas o arquivo é necessário para o Google Drive de qualquer forma, e sem o arquivo em disco a retomada — que é o que salva um upload de 2 GB numa rede instável — fica muito mais difícil. O ganho não paga o risco.
🖥️ Telas (web resources do Dynamics)
As telas em pages/ não são servidas por este servidor: são web resources
publicados no Dynamics, em https://ibgc.crm2.dynamics.com. Os arquivos aqui
são a fonte versionada — depois de alterá-los, é preciso publicar no Dynamics.
Por isso a origem das requisições é a do Dynamics, e não a do próprio servidor.
É o valor que deve entrar em CORS_ORIGINS. Confirme no DevTools qual valor
chega no cabeçalho Origin antes de restringir.
| Tela | Papel |
|---|---|
zoomVimeo.html | dispara o processamento e publica no Canvas |
vimeoCanvas.html | só a publicação no Canvas |
ConfigAPI.html | credenciais das integrações e limpeza de disco |
Cada tela é um arquivo único: CSS, JavaScript e a biblioteca socket.io estão
todos embutidos, sem nenhuma referência externa. No Dynamics cada web resource é
um item separado, então quanto menos arquivos para publicar e manter em
sincronia, melhor — e um <script src> de CDN ainda dependeria da política de
conteúdo do ambiente.
Para atualizar a biblioteca embutida:
npm install --save-dev socket.io-client@<versao>
npm run telas:embutir
O bloco embutido fica no fim do arquivo, depois do seu código: io() só é
chamado dentro de funções, e todo script inline executa antes do evento load.
Assim o que você edita continua no começo do arquivo, sem dezenas de KB de
código minificado na frente.
Como a tela acompanha um processamento
O que estava quebrado nas telas
Pendência conhecida:
vimeoCanvas.htmlainda tem uma funçãoacessarZoom()copiada da outra tela, referenciando uminputZoomque não existe ali. É código morto — não quebra nada porque nada a chama, mas vale remover numa próxima passada.
📊 Operação e diagnóstico
Painel
https://sisnode.ibgc.org.br/painel — página única servida pelo próprio Node,
sem dependência externa. Pede o mesmo login do sistema e atualiza a cada 5 s:
disco livre, tamanho da pasta de vídeos, fila, concluídos e falhas nas últimas
24 h, tempo médio de processamento, estado do Redis, e a lista dos trabalhos
recentes com etapa, progresso, duração e o erro de cada um.
Endpoints
| Rota | Acesso | Para quê |
|---|---|---|
GET /health | público | o proxy saber se o processo está vivo |
GET /health/completo | autenticado | disco, fila, dependências, alertas |
GET /jobs | autenticado | trabalhos recentes |
GET /jobs/:id | autenticado | estado de um trabalho |
/health/completo monta uma lista de alertas em vez de deixar os números
para alguém interpretar: disco abaixo do mínimo, Redis fora do ar, credencial
faltando, ou mais falhas do que sucessos nas últimas 24 h.
Log
Cada linha traz horário, nível, escopo e o contexto do trabalho:
04:18:39.260 INFO [fila] job=a43a1d15 iniciando (1/2 em execução, 0 na fila)
04:18:39.272 INFO [fila] job=52955c2d iniciando (2/2 em execução, 0 na fila)
04:18:39.767 ERROR [executor] job=52955c2d idReuniao=85587654321 falhou: Gravação não encontrada.
LOG_FORMATO=json troca para uma linha JSON por evento, quando o log for
coletado por alguma ferramenta.
O que era antes:
console.logsolto, sem horário, sem escopo e sem identificar a gravação. Com vários processamentos ao mesmo tempo as linhas se misturavam, e responder "por que esse vídeo demorou?" exigia adivinhação.
Encerramento controlado
Ao receber SIGTERM ou SIGINT — que é o que um systemctl restart ou um
deploy envia — o serviço fecha os websockets, para de aceitar conexões, dá até
30 s para os trabalhos em execução terminarem, e marca como interrompido o
que não terminou, com o motivo visível em GET /jobs/:id.
O que era antes: o processo morria no meio de um download de 2 GB. O arquivo parcial ficava no disco, o trabalho ficava eternamente "em andamento", e o usuário nunca descobria o que aconteceu — a tela simplesmente parava.
🧪 Verificações
npm test
check:camadas— falha se alguma dependência atravessar a fronteira errada (uma rota importandoaxios, um cliente de API importandoexpress, …).check:smoke— sobe o servidor com o Redis fora do ar e confere que toda rota responde. Foi assim que a request pendurada do Redis foi encontrada.
🚀 Instalação
npm install
cp .env.example .env
npm start
Requer Node.js >= 20 e um Redis acessível em REDIS_URL.
Credenciais: o
.envé a fonte de verdade. Oconfig.jsoné opcional e guarda apenas o que for alterado pela tela/configem runtime. Nenhum dos dois é versionado.
📡 Documentação Rápida da API
Autenticação
POST /login
Retorna o token JWT necessário para as demais requisições.
Disparar Processo
POST /zoom/:id
Inicia o pipeline para o ID da gravação do Zoom fornecido.
- Body:
{ "NomesDosInstrutores": "Nome..." }
Publicar no Canvas
POST /canvas/course/:idCourse/:idZoom
Cria a página no curso do Canvas vinculando o vídeo processado.
⚙️ Gerenciamento de Configurações
As credenciais do sistema podem ser atualizadas via API para facilitar a manutenção:
- Vimeo:
GET/POST /config/update-token-vimeo - Zoom:
GET/POST /config/update-zoom-credenciais - Canvas:
GET/POST /config/update-canvas-credenciais
Desenvolvido para otimizar a automação de vídeos. Para IBGC 🎓