277 lines
11 KiB
Markdown
277 lines
11 KiB
Markdown
# Leads Extractor
|
||
|
||
Extrator assíncrono de canais de podcast, entrevistados e contatos públicos. O
|
||
backend usa Rust, Actix, Tokio, Chromiumoxide, PostgreSQL e OpenRouter; o frontend
|
||
usa SvelteKit, TypeScript e Tailwind CSS.
|
||
|
||
## Arquitetura
|
||
|
||
- A API Actix responde rápido e grava execuções na fila PostgreSQL.
|
||
- Workers Tokio reclamam jobs com `FOR UPDATE SKIP LOCKED` e executam os
|
||
pipelines com concorrência configurável.
|
||
- Cada navegação de documento e busca segue `perfil limpo -> aquecer a mesma
|
||
origem -> acessar` no Chromium headless. Assim, TLS, HTTP/2, Client Hints,
|
||
cookies e carregamento de assets são produzidos pelo navegador real; a sessão
|
||
e o processo são descartados ao fim. Downloads binários continuam em um
|
||
cliente HTTP isolado, sem reaproveitar conexões.
|
||
- A IA recebe DTOs preparados pelo backend e nunca possui acesso ao banco.
|
||
- Contatos são normalizados e deduplicados, preservando todas as origens e
|
||
evidências.
|
||
- O Docker Compose local executa somente o PostgreSQL; API e interface rodam
|
||
manualmente, facilitando logs, recompilação e depuração.
|
||
|
||
## Requisitos locais
|
||
|
||
- Docker com Compose
|
||
- Rust 1.96 ou compatível com edition 2024
|
||
- Node.js 24+ e npm
|
||
- Google Chrome/Chromium disponível no `PATH` para execução fora do container
|
||
|
||
## Desenvolvimento local
|
||
|
||
1. Inicie somente o PostgreSQL. O usuário, banco e senha `leads` são defaults
|
||
exclusivamente locais:
|
||
|
||
```bash
|
||
docker compose up -d postgres
|
||
```
|
||
|
||
Se a porta `5432` estiver ocupada:
|
||
|
||
```bash
|
||
POSTGRES_PORT=55432 docker compose up -d postgres
|
||
```
|
||
|
||
Nesse caso, ajuste também a porta em `backend/.env`.
|
||
|
||
2. Prepare o backend:
|
||
|
||
Crie `backend/.env` com as variáveis descritas na seção
|
||
[Variáveis de ambiente](#variáveis-de-ambiente) abaixo.
|
||
|
||
Edite apenas a cópia local `backend/.env`. Além do OpenRouter e da DataImpulse,
|
||
defina um usuário de acesso e uma senha exclusiva com pelo menos 12
|
||
caracteres:
|
||
|
||
- `AUTH_USERNAME`: obrigatório; não possui default seguro.
|
||
- `AUTH_PASSWORD`: obrigatória; mínimo de 12 caracteres.
|
||
- `AUTH_SESSION_TTL_SECS=43200`: duração padrão da sessão, em segundos.
|
||
- `AUTH_COOKIE_SECURE=false`: somente para HTTP local; use `true` sob HTTPS.
|
||
- `AUTH_COOKIE_SAME_SITE=strict`: default recomendado; `lax` também é
|
||
suportado, mas nenhum dos modos habilita autenticação cross-site.
|
||
|
||
Em seguida, inicie a API manualmente:
|
||
|
||
```bash
|
||
cd backend
|
||
cargo run
|
||
```
|
||
|
||
O backend cria `schema_migrations` e aplica as migrations pendentes no
|
||
startup. O PostgreSQL não executa SQL via `docker-entrypoint-initdb.d`.
|
||
`SERVER_PORT` é configurável e usa `8080` por padrão.
|
||
|
||
3. Em outro terminal, prepare o frontend:
|
||
|
||
```bash
|
||
cd frontend
|
||
npm ci
|
||
```
|
||
|
||
Crie `frontend/.env` com as variáveis descritas na seção
|
||
[Variáveis de ambiente](#variáveis-de-ambiente) abaixo, então:
|
||
|
||
```bash
|
||
npm run dev
|
||
```
|
||
|
||
Se a porta `8080` estiver ocupada, escolha outra em `SERVER_PORT` no
|
||
`backend/.env` e defina a mesma porta em `VITE_API_URL` no `frontend/.env`
|
||
antes de iniciar os dois processos.
|
||
|
||
4. Abra `http://localhost:5173` e autentique-se com as credenciais definidas no
|
||
`backend/.env`. Por padrão, a API local fica em `http://localhost:8080`.
|
||
|
||
## Operação local
|
||
|
||
O Compose não constrói nem inicia backend ou frontend. Ele gerencia somente o
|
||
PostgreSQL 18 pela imagem `postgres:18`:
|
||
|
||
```bash
|
||
docker compose config --quiet
|
||
docker compose up -d postgres
|
||
docker compose ps postgres
|
||
```
|
||
|
||
Execute `cargo run` em `backend/` e `npm run dev` em `frontend/`, em terminais
|
||
separados. Os endpoints locais são:
|
||
|
||
- Frontend: `http://localhost:5173`
|
||
- Backend: `http://localhost:8080`
|
||
- Healthcheck: `http://localhost:8080/api/health`
|
||
|
||
Para acompanhar ou encerrar apenas o banco:
|
||
|
||
```bash
|
||
docker compose logs -f postgres
|
||
docker compose down
|
||
```
|
||
|
||
`docker compose down` mantém o volume PostgreSQL. `docker compose down -v`
|
||
remove o banco e causa perda de dados; use apenas quando isso for intencional.
|
||
|
||
### Publicação
|
||
|
||
O Compose fornecido é destinado ao banco local. Em publicação, execute backend
|
||
e frontend com o gerenciador de processos ou orquestrador escolhido e mantenha
|
||
senhas, chave da OpenRouter e credenciais da DataImpulse em um gerenciador de
|
||
segredos. Defina uma senha PostgreSQL forte, `FRONTEND_ORIGIN` com a origem
|
||
HTTPS exata e `AUTH_COOKIE_SECURE=true`. Não publique a API sem TLS.
|
||
|
||
A sessão de autenticação usa cookie `HttpOnly`, enviado pelo frontend com
|
||
credenciais incluídas, e política `SameSite` restritiva. O frontend usa
|
||
`POST /api/auth/login`, `GET /api/auth/session` e `POST /api/auth/logout`.
|
||
|
||
Frontend e API precisam permanecer **same-site**, inclusive quanto ao esquema
|
||
HTTP/HTTPS. CORS não torna cookies `SameSite=Strict` ou `Lax` utilizáveis entre
|
||
sites diferentes, portanto implantação cross-site não é suportada. Em
|
||
desenvolvimento, use o mesmo hostname nas duas URLs — por exemplo, não misture
|
||
`localhost` e `127.0.0.1`. Se forem origens diferentes dentro do mesmo site,
|
||
configure `FRONTEND_ORIGIN` com a origem exata do frontend.
|
||
|
||
O cliente adiciona automaticamente `X-CSRF-Protection: 1` a toda requisição
|
||
mutante, envia o cookie com `credentials: include` e o navegador fornece
|
||
`Origin`. Clientes manuais precisam enviar o mesmo cabeçalho e a origem
|
||
esperada. O login possui rate limit; excesso de tentativas recebe HTTP `429`.
|
||
Respostas privadas usam `Cache-Control: no-store` para evitar retenção por
|
||
caches do navegador ou de intermediários.
|
||
|
||
As sessões atuais vivem somente na memória do processo backend. Execute uma
|
||
única instância: reiniciar ou substituir o processo encerra todas as sessões e
|
||
exige novo login. Antes de usar múltiplas réplicas, mova o armazenamento de
|
||
sessões para um serviço compartilhado, como PostgreSQL ou Redis.
|
||
|
||
## Variáveis de ambiente
|
||
|
||
### Compose
|
||
|
||
| Variável | Default local | Quando alterar |
|
||
| --- | --- | --- |
|
||
| `POSTGRES_DB` | `leads_extractor` | Nome do banco |
|
||
| `POSTGRES_USER` | `leads` | Usuário do banco |
|
||
| `POSTGRES_PASSWORD` | `leads` | Obrigatória fora do ambiente local |
|
||
| `POSTGRES_PORT` | `5432` | Porta publicada no host |
|
||
|
||
Se alterar usuário, senha, banco ou porta do Compose, atualize também
|
||
`DATABASE_URL` em `backend/.env`. Senhas com caracteres reservados precisam
|
||
estar percent-encoded nessa URL.
|
||
|
||
### Backend
|
||
|
||
Referência completa das variáveis: `backend/src/config.rs` e o `.env` na raiz
|
||
(usado pelo Compose).
|
||
|
||
- `OPENROUTER_API_KEY`: obrigatória para resumos, categorização e extração por
|
||
IA; o servidor pode iniciar sem ela, mas esses fluxos falham de forma
|
||
explícita.
|
||
- `OPENROUTER_PRIMARY_MODEL` / `OPENROUTER_FALLBACK_MODEL`, `OPENROUTER_BASE_URL`,
|
||
timeouts e retries: configuração do provedor de IA (OpenRouter). Se o
|
||
modelo principal estiver indisponível ou falhar, o próprio OpenRouter tenta
|
||
automaticamente o modelo de fallback na mesma requisição; deixe
|
||
`OPENROUTER_FALLBACK_MODEL` vazio para desativar o fallback.
|
||
- `AUTH_USERNAME`: usuário obrigatório para acessar a aplicação; não há valor
|
||
padrão seguro.
|
||
- `AUTH_PASSWORD`: senha obrigatória com pelo menos 12 caracteres.
|
||
- `AUTH_SESSION_TTL_SECS`: validade da sessão; default `43200` segundos.
|
||
- `AUTH_COOKIE_SECURE`: `false` apenas durante desenvolvimento HTTP local e
|
||
`true` quando a aplicação estiver sob HTTPS.
|
||
- `AUTH_COOKIE_SAME_SITE`: `strict` por padrão ou `lax`; ambos exigem frontend
|
||
e API same-site.
|
||
- `SERVER_HOST` e `SERVER_PORT`: endereço da API; a porta padrão é `8080`.
|
||
- `BROWSER_MIN_NAVIGATION_DELAY_MS` / `BROWSER_MAX_NAVIGATION_DELAY_MS`:
|
||
intervalo aleatório aplicado antes de cada sessão de navegador (padrão:
|
||
2500–15000 ms).
|
||
- `BROWSER_MACRO_PAUSE_*`: faixa de sessões e duração das pausas maiores;
|
||
padrões de 15–20 sessões e 30–90 segundos.
|
||
- `FRONTEND_ORIGIN`: origem exata autorizada pelo CORS e pela validação das
|
||
requisições mutantes.
|
||
- `DATAIMPULSE_PROXY_ENABLED`: fica ativo por padrão; desative somente para
|
||
diagnóstico local explícito.
|
||
- `DATAIMPULSE_PROXY_USERNAME` e `DATAIMPULSE_PROXY_PASSWORD`: obrigatórias
|
||
quando o proxy estiver ativo.
|
||
- `DATAIMPULSE_PROXY_HOST`, porta e país: parâmetros da DataImpulse. O login
|
||
efetivo nunca inclui `sessid`, portanto cada requisição usa o pool rotativo.
|
||
- `MAX_INTERVIEWEES_PER_RUN`: teto operacional por execução, aplicado também
|
||
sob concorrência. `0` processa todos; para o teste externo controlado use
|
||
`100` (valor já usado no `.env` local desta instalação).
|
||
- `WORKER_CONCURRENCY`, `BROWSER_CONCURRENCY`, limites de crawl e timeouts:
|
||
orçamento operacional.
|
||
- `MEDIA_DIR`: diretório local de logos, imagens e demais mídias coletadas.
|
||
- `BROWSER_NO_SANDBOX`: mantenha `false` localmente; habilite somente em um
|
||
ambiente isolado que não ofereça user namespaces.
|
||
|
||
Nunca comite `backend/.env`, `frontend/.env`, tokens, senhas, cookies ou
|
||
credenciais do proxy. Os `.dockerignore` também excluem esses arquivos do
|
||
contexto de build, mas isso não substitui um gerenciador de segredos.
|
||
|
||
### Frontend
|
||
|
||
Referência completa das variáveis: `.env` na raiz (usado pelo Compose).
|
||
|
||
- `VITE_API_URL`: URL da API; localmente, `http://localhost:8080`. Deve usar o
|
||
mesmo `SERVER_PORT` configurado no backend.
|
||
- `VITE_API_POLL_INTERVAL`: intervalo de atualização do painel, em
|
||
milissegundos.
|
||
- `VITE_DEMO_MODE`: mantenha `false` para autenticação e dados reais. `true`
|
||
ativa somente a demonstração local explícita.
|
||
|
||
## Dados persistentes
|
||
|
||
- `postgres_data`: cluster PostgreSQL 18 gerenciado pelo Compose.
|
||
- `MEDIA_DIR`: diretório no host usado pelo backend iniciado manualmente para
|
||
logos, imagens e demais mídias coletadas.
|
||
|
||
Faça backup do volume e do diretório de mídia. Alterar ou adicionar migrations
|
||
não recria o banco: o backend registra cada versão aplicada em
|
||
`schema_migrations`.
|
||
|
||
## Fluxos
|
||
|
||
- **Busca:** procura canais de podcast e permite revisar a lista.
|
||
- **Entrevistados:** coleta vídeos, metadados e legendas; a IA identifica todos
|
||
os convidados e resolve duplicidades. A ordem é português/original
|
||
preferido e depois qualquer faixa original disponível, sem tradução
|
||
artificial. Transcrições longas são processadas em trechos sobrepostos.
|
||
- **Contatos:** pesquisa o nome/contexto no Google e percorre páginas em BFS até
|
||
cinco níveis, classificando cada contato como pessoal ou comercial. Links
|
||
adicionais escolhidos pela IA só são visitados quando possuem evidência na
|
||
página e entram novamente no mesmo ciclo agentivo até o quinto nível.
|
||
|
||
Falhas externas passam pelos retries com backoff, jitter, sessão/IP novo e
|
||
timeout de cada módulo. Se um item ainda falhar, o job é repetido e reaproveita
|
||
vídeos já concluídos como checkpoints. Falta de créditos pausa o mesmo job sem
|
||
perder a possibilidade de retomada pelo botão de retry. A resolução de
|
||
identidades possui um limite global de concorrência para reservar conexões do
|
||
pool PostgreSQL mesmo com várias execuções simultâneas.
|
||
|
||
## Verificações
|
||
|
||
```bash
|
||
docker compose config --quiet
|
||
docker compose up -d postgres
|
||
|
||
cd backend
|
||
cargo fmt --all -- --check
|
||
cargo check --all-targets
|
||
cargo test --all-targets
|
||
cargo clippy --all-targets -- -D warnings
|
||
|
||
cd ../frontend
|
||
npm ci
|
||
npm run check
|
||
npm run build
|
||
```
|
||
|
||
O crawler aceita apenas URLs HTTP/HTTPS públicas e bloqueia destinos locais ou
|
||
privados. Não há automação de login, CAPTCHA ou áreas protegidas.
|