Documento de Arquitetura — Followw UnB
Introdução
Finalidade
Este documento descreve a arquitetura de software do Followw UnB, apresentando uma visão abrangente do sistema por meio de múltiplos modelos arquiteturais (Modelo C4), padrões de projeto, decisões técnicas e organização do código.Escopo
O Followw UnB é um cliente alternativo e open-source para o ecossistema de sistemas acadêmicos da Universidade de Brasília (UnB), agregando em uma experiência unificada e performática dados do SIGAA, Restaurante Universitário (RU), calendários e editais. O sistema não possui contas proprietárias: a autenticação é delegada ao Centro de Autenticação da UnB e toda requisição para o SIGAA é stateless, com credenciais cifradas no cliente e sincronização assíncrona inteligente.Stack Tecnológica
Diagramas da Arquitetura (Modelo C4)
Os diagramas de arquitetura adotam o padrão de Diagrams as Code via C4-PlantUML, versionados como código emdocs/diagrams/ e renderizados em formato vetorial SVG localmente
através do script scripts/generate_diagrams.py.
Diagrama de Contexto do Sistema (Nível 1)
Apresenta o Followw UnB no seu ambiente operacional, seus usuários e as integrações com os sistemas da universidade e serviços externos.
Código-fonte: docs/diagrams/context.puml
Diagrama de Containers (Nível 2)
Detalha as aplicações executáveis, armazenamento de dados e fronteiras de comunicação entre componentes e serviços.
Código-fonte: docs/diagrams/container.puml
Diagrama de Componentes (Nível 3)
Apresenta a organização interna dos componentes do Frontend (apps/web), do Backend (apps/api) e
suas interações com as bibliotecas do monorepo e dados.
Componentes do Frontend
Código-fonte: docs/diagrams/components_frontend.puml
Componentes do Backend
Código-fonte: docs/diagrams/components_backend.puml
Padrões Arquiteturais e Decisões de Design
Sessão Stateless Criptografada (JWE)
- Zero-Storage de Senhas: A API do Followw não persiste credenciais nem sessões em disco ou banco de dados.
- Cookies HttpOnly JWE: A sessão é dividida em dois cookies protegidos por criptografia
autenticada (
AES-256-GCM), com chaves distintas derivadas via HKDF a partir deJWT_SECRET_KEY:access_token(40 min): Sessão JSF ativa do SIGAA.refresh_token(14 dias): Matrícula e senha encriptadas para renovação automática de sessão sem atrito para o usuário.
Proteção contra Abuso e Rate Limiting (Redis)
- Controle de Taxa Desacoplado (
api/rate_limit.py,api/redis.py): A API aplica limites de requisição em janelas fixas de 60 segundos gerenciadas no Redis (Upstash Redisvia REST HTTP em produção eredis:8localmente), sem onerar o PostgreSQL ou o SIGAA, conforme documentado emdocs/rate-limit.md. - Cotas Diferenciadas:
- Rotas Gerais: Limite global de 600 requisições por IP a cada 60 s, absorvendo navegações e chamadas legítimas de redes universitárias (sob NAT).
- Autenticação (
POST /auth/sigaa): Cota estrita de 30 tentativas por IP a cada 60 s, prevenindo ataques de força bruta contra o CAS institucional.
- Privacidade por Design e Anonimização de IP: Os identificadores de chave no Redis são gerados via
HMAC-SHA256 do IP do cliente utilizando chave derivada de
JWT_SECRET_KEY. Endereços IPv6 são agrupados por prefixo/64. Nenhum dado pessoal, cookie ou credencial é gravado no Redis. - Encaminhamento Seguro de IP no SSR: No renderizador SSR (
apps/web), o IP original do cliente é repassado à API assinado criptograficamente com HMAC-SHA256 e expiração de 60 s viaRATE_LIMIT_SECRET_KEY, impedindo falsificação de cabeçalhos ou esgotamento de cotas pelo IP de saída do servidor web. - Tolerância a Falhas (Fail-Open): Caso o Redis apresente timeout (máximo de 1 s) ou instabilidade de rede, a requisição é liberada automaticamente sem interromper a navegação do estudante, registrando evento operacional no log.
Estratégia de Cache: Stale-While-Revalidate (SWR)
- Chamadas a dados custosos do SIGAA utilizam o
Sync.resolve(tarefa, alvo, load)(api/sync/). - Se o dado em cache estiver válido, ele é servido imediatamente.
- Se estiver expirado (stale), os dados do cache são servidos instantaneamente e a tarefa de sincronização entra num job do QStash, publicado antes da resposta.
- Sem cache, a requisição busca no SIGAA na hora, com a sessão do próprio usuário.
Isolamento dos Scrapers em Pacotes Monorepo
- O parsing das páginas da UnB é completamente desacoplado da API REST em dois pacotes
(
packages/sigaa-clientepackages/unb-browser). - Resiliência a JSF: O
sigaa-clienttrata ciclos de vida deViewState, postbacks de menus laterais (jscookMenu) e a turma aberta, que é estado da sessão: requisições paralelas na mesma sessão podem trocá-la, e o client confere e reabre a turma antes de ler cada tela. - Extração geométrica de PDFs: O
unb-browserutilizapdfplumberavaliando posicionamento espacial relativo para lidar com células mescladas e formatos variados entre os campi da UnB.
Orquestração de Background Jobs (QStash)
- Operações lentas de sincronização são desacopladas da requisição síncrona do usuário.
- Sessão efêmera por job: o job nunca usa a sessão do usuário. Ele leva a credencial cifrada (Fernet com chave própria derivada via HKDF), faz login numa sessão própria do SIGAA, roda as tarefas e faz logout no fim. Sessões diferentes da mesma conta não interferem entre si.
- Um job por turma: a requisição junta as tarefas vencidas e publica um job por turma (e um da conta, com perfil e lista de turmas). O sync do login abre um job para cada turma com telas vencidas.
- Paralelismo: o flow control por matrícula (
parallelism: 4) só poupa o CAS de rajadas de login; os jobs não disputam sessão. - Falhas: uma tarefa que falha na origem não impede as outras e ganha uma única retentativa, com 1 min de espera. Senha recusada descarta o job.
Arquitetura Offline-First e PWA (Frontend)
- O cliente web funciona sem conexão à internet combinando duas camadas independentes em
apps/web/src/integrations/offline/(detalhadas emdocs/offline.md):- Service Worker (
vite-plugin-pwa/ Workbox): faz precache dos assets de build e armazena o HTML das navegações SSR em estratégia network-first (com timeout de 3 s) em cache versionado por build (followw-pages-<build>). - Persistência do TanStack Query (
IndexedDB): salva os dados retornados pela API por até 14 dias (idb-keyval), restaurando-os após a hidratação e descartando o cache automaticamente quando o contrato da API muda (via hash SHA-256 deschema.gen.ts).
- Service Worker (
Seed Estático de Dados Públicos e Regras do Calendário Acadêmico
- Semeadura de Dados (
api/db/seed/): na inicialização do banco (db-init), arquivos CSV pré-coletados empackages/sigaa-client/data/populam automaticamente unidades acadêmicas (units), cursos de graduação (courses), estruturas curriculares (curriculums), componentes curriculares (subjects) e turmas (classrooms). - Congelamento por Calendário Acadêmico (
api/academic_calendar.py): utiliza as datas oficiais do período letivo para congelar automaticamente a ressincronização de turmas encerradas (passados 3 dias do término do semestre, após um último sync consolidado), evitando requisições desnecessárias ao SIGAA em semestres passados.
Observabilidade Estruturada e Privacidade por Design
- Logs Estruturados (
api/logger.py): a API emite eventos em formato JSON por linha emstderr, correlacionando todas as operações da requisição (sigaa.connect,queue.publish,job,sync.task) sob um mesmorequest_ide registrando o tempo decorrido (duration_ms), conforme detalhado emdocs/observability.md. - Allowlist Estrita de Campos: o logger aceita apenas metadados operacionais pré-definidos, garantindo por construção que cookies, cabeçalhos, parâmetros de URL, senhas ou tracebacks com dados pessoais jamais sejam gravados nos logs.