Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 125 additions & 0 deletions docs/blog/posts/2026-03-06-scraper-telegram-keycloak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
---
date: 2026-03-06
authors:
- nitai
categories:
- Scraper
- Push Notifications
- Infraestrutura
- Autenticação
title: "Três frentes em paralelo: scraper, Telegram e Keycloak"
hide:
- toc
---

# Três frentes em paralelo: scraper, Telegram e Keycloak

Entre 4 e 6 de março, o projeto avançou em paralelo em três direções distintas: o scraper ganhou uma otimização de performance que evita re-processar artigos já conhecidos, o serviço de push notifications cresceu de WebPush para um ecossistema Telegram completo, e a infraestrutura de autenticação ganhou um SSO centralizado com Keycloak.

<!-- more -->

## Contexto

O último post parou em 4 de março, com o portal v1 no ar e o bot Telegram fazendo scraping ao vivo. Mas naquele mesmo dia, antes de dormir, já tinha três frentes abertas em paralelo. Cada PR que mergeiava abria espaço para o próximo.

---

## Frente 1: Scraper — performance e qualidade de dados

### Dia 1 (04/03): Known URL fence

O scraper tinha um problema silencioso: a cada execução, ele refazia o fetch de artigos que já estavam no banco. Sem nenhum mecanismo de deduplicação no nível do loop de scraping, cada DAG processava tudo do zero.

A solução foi o [known URL fence (scraper#12)](https://github.com/destaquesgovbr/scraper/pull/12): antes de iniciar o scraping de uma agência, o sistema consulta o PostgreSQL e carrega as URLs recentes. Durante o loop, artigos com URL já conhecida são pulados sem fazer fetch da página. E se 3 artigos consecutivos forem conhecidos, o sistema encerra a busca antecipadamente — o "fence".

```
Listagem da agência
├── Artigo 1 → URL nova → scrape
├── Artigo 2 → URL nova → scrape
├── Artigo 3 → URL conhecida → skip
├── Artigo 4 → URL conhecida → skip
├── Artigo 5 → URL conhecida → skip ← 3 consecutivos → FENCE
└── (para aqui, não processa o resto)
```

Backward-compatible: sem `known_urls`, o comportamento é idêntico ao atual. O PR saiu com 11 testes unitários escritos em TDD (testes primeiro), adicionados à suite de 98 testes.

### Dia 1-2 (04-05/03): Qualidade dos dados

Com o scraper otimizado, vieram dois fixes de qualidade:

- [scraper#14](https://github.com/destaquesgovbr/scraper/pull/14): URLs de agências com falha persistente (403, 404) foram corrigidas ou desativadas, resolvendo issues de data quality que já estavam abertas.
- [scraper#17](https://github.com/destaquesgovbr/scraper/pull/17) (por [@mauriciomendonca](https://github.com/mauriciomendonca)): o algoritmo de limpeza de conteúdo estava removendo texto legítimo. A heurística foi ajustada para ser menos agressiva, preservando mais do conteúdo original dos artigos.

---

## Frente 2: Push Notifications → ecossistema Telegram

Esta foi a frente mais intensa. O serviço `push-notifications` saiu de WebPush simples para um sistema com DMs Telegram, canais por órgão e filtros expandidos.

### Dia 1 (04/03): Filtros expandidos e Telegram rico

Dois PRs grandes mergeiram em sequência:

**[push-notifications#1](https://github.com/destaquesgovbr/push-notifications/pull/1) — filtros v2:** as subscriptions passaram a suportar filtros por themes L2/L3, tags e keywords (além de agency e theme_l1 que já existiam). Adicionado cooldown de 15min entre notificações para o mesmo subscriber — sem isso, um burst de artigos de uma agência spammaria todos os inscritos. O campo `user_id` foi adicionado para vincular subscriptions a usuários autenticados no portal.

**[push-notifications#2](https://github.com/destaquesgovbr/push-notifications/pull/2) — Telegram com formato rico:** criado `telegram_sender.py` com envio de foto via `sendPhoto` e fallback para texto quando a imagem falha. Formato da notificação: agência, categoria, título, resumo, data e link. Usuários se inscrevem direto no bot Telegram, que gerencia assinaturas via webhook. Se o bot for bloqueado pelo usuário, a assinatura é removida automaticamente.

A infra para rodar o bot no Cloud Run chegou logo depois com [infra#116](https://github.com/destaquesgovbr/infra/pull/116) e um fix de token com [infra#117](https://github.com/destaquesgovbr/infra/pull/117).

### Dia 2 (06/03): Summary no WebPush e canais por órgão

Com o bot funcionando para DMs, a pergunta natural foi: e os canais de órgão? Em vez de cada pessoa se inscrever individualmente, canais Telegram dos próprios órgãos recebem automaticamente os artigos publicados.

**[push-notifications#4](https://github.com/destaquesgovbr/push-notifications/pull/4):** adicionado `summary` ao payload WebPush — o lead editorial do artigo agora aparece na notificação do navegador, não só o título.

**[push-notifications#5](https://github.com/destaquesgovbr/push-notifications/pull/5) — canais por órgão:** novo módulo `telegram_channel_sender.py`. Quando um artigo é publicado, além das DMs individuais, o sistema faz posting automático no canal Telegram do órgão (se registrado). Admins registram canais via comandos `/canal` e `/canais` no bot. Auto-desativação após 5 falhas consecutivas. Saiu com 29 novos testes, totalizando 114 na suite.

As env vars correspondentes chegaram com [infra#119](https://github.com/destaquesgovbr/infra/pull/119).

---

## Frente 3: Keycloak SSO

### Dia 2 (06/03): Do zero ao SSO em produção

O portal usava Google OAuth diretamente via NextAuth. A ideia era centralizar a autenticação num SSO que, futuramente, suportaria Gov.br como identity provider — sem precisar mexer no código do portal toda vez que o IdP mudar.

A escolha foi Keycloak, deployado no Cloud Run:

**[infra#120](https://github.com/destaquesgovbr/infra/pull/120):** toda a infra Keycloak em Terraform — database no Cloud SQL existente, service account dedicada, Artifact Registry para imagens Docker, Cloud Run com 2 vCPU, 1Gi e `min=1` (always-on), 4 secrets no Secret Manager, IAM granular. Seguiu exatamente o padrão de `federation.tf` que já existia no repo.

**[infra#121](https://github.com/destaquesgovbr/infra/pull/121):** fix imediato — as health probes estavam apontando para a porta padrão, mas o Keycloak expõe management na porta 9000.

**[portal#98](https://github.com/destaquesgovbr/portal/pull/98):** a migração no portal foi zero código. O `auth.ts` já suportava OIDC genérico via `AUTH_GOVBR_ISSUER`. Bastou trocar a variável de ambiente apontando para o Keycloak DGB e substituir os secrets do Google pelos do Keycloak.

```
Antes: Usuário → Portal → NextAuth → Google
Depois: Usuário → Portal → NextAuth → Keycloak → Google (upstream IdP)
```

A camada extra do Keycloak parece overhead, mas abre espaço para adicionar Gov.br, autenticação de parceiros e políticas de acesso sem tocar no código do portal.

---

## Números

| Frente | PRs | Repos | Testes |
|--------|-----|-------|--------|
| Scraper | 4 | scraper | 98 → 109 |
| Push Notifications | 6 | push-notifications, infra | 77 → 114 |
| Keycloak SSO | 3 | infra, portal | — |
| **Total** | **13** | **4** | **+46** |

---

## Lições

1. **Trabalho paralelo funciona quando as frentes são independentes.** Scraper, push notifications e Keycloak não tinham dependências entre si — cada frente avançou no próprio ritmo sem bloquear as outras.

2. **TDD ajuda mais em otimizações do que em features novas.** O known URL fence foi escrito com testes primeiro porque a lógica de skip+fence tinha muitos edge cases (backward compat, sem known_urls, fence count). Os testes capturaram 2 bugs antes do PR.

3. **Infraestrutura como código paga dividendos.** Subir o Keycloak no Cloud Run foi possível porque o padrão `federation.tf` já estava estabelecido. Copiar a estrutura e ajustar os valores levou menos tempo do que configurar via console.

4. **Auto-desativação é essencial para sistemas de notificação.** Bots bloqueados e canais inacessíveis acumulam silenciosamente. A remoção automática de assinaturas falhas (e o contador de falhas nos canais) evita que o sistema tente em loop entregar para destinos mortos.
Loading