Skip to content

feat(scraper): implementar lock distribuído para impedir execuções simultâneas (PAV-119) - #229

Open
RuhanFreitas wants to merge 5 commits into
developfrom
feature/pav-119-lock-distribuido-scraper
Open

feat(scraper): implementar lock distribuído para impedir execuções simultâneas (PAV-119)#229
RuhanFreitas wants to merge 5 commits into
developfrom
feature/pav-119-lock-distribuido-scraper

Conversation

@RuhanFreitas

Copy link
Copy Markdown
Member

Card

  • PAV-119

O que foi feito

  • Implementa lock distribuído no Valkey (scraper:run:lock) compartilhado por cron, disparo manual e cache miss de /scrape, com aquisição atômica SET NX PX, renovação e liberação por token via Lua.
  • Propaga fail-closed e contratos HTTP estáveis: 409 SCRAPER_ALREADY_RUNNING, 503 SCRAPER_RUN_LOCK_UNAVAILABLE e 503 SCRAPER_RUN_LOCK_LOST, preservados no backend admin sem auditar disparos rejeitados.
  • Adiciona TTL/renovação configuráveis (SCRAPER_RUN_LOCK_TTL, SCRAPER_RUN_LOCK_RENEW_INTERVAL), estado operacional (runId separado do token), cancelamento seguro em perda de lock, espera de liberação no graceful shutdown e documentação/env/Compose.

Validação

  • go test ./... em scraper-go
  • npm test / testes de backend/tests/unit/modules/admin/scrapers.test.ts
  • Com scraper ativo, POST /admin/scrape retorna 409 e não inicia segunda execução
  • Confirmar no Valkey: GET scraper:run:lock, PTTL, HGETALL scraper:run:state (sem expor token no state)
  • Após término/shutdown controlado, lock e state são removidos
  • Com Valkey indisponível, disparo manual retorna 503 SCRAPER_RUN_LOCK_UNAVAILABLE

hltav and others added 5 commits August 17, 2026 19:22
## Card
-

https://linear.app/tatame/issue/PAV-104/melhoria-003-permitir-acesso-a-notificacao-por-meio-do-clique-no

## O que foi feito
- Notificações vinculadas a uma vaga (salvamento ou mudança de status)
agora são clicáveis: o clique navega até `/dashboard?jobId={id}` e abre
automaticamente o modal de detalhes da vaga correspondente.
- O `jobId` é resolvido a partir do campo `entityId`/`entityType` já
existente na tabela de notificações do backend, então o clique funciona
tanto para notificações recém-criadas (locais) quanto para notificações
já persistidas e recarregadas da API.
- Ao clicar em uma notificação, ela é marcada como lida (chamada a
`PATCH /notifications/:id/read`) e passa a ser exibida com um indicador
visual diferente (bolinha cinza no lugar da verde), permanecendo na
lista em vez de desaparecer.
- Notificações sem vínculo a uma vaga (ex.: mensagens de sistema/mentor)
continuam sem interação de clique, sem alteração de comportamento.

## Validação
```bash
npm run lint --workspace=frontend
npm run test --workspace=backend
npm run build --workspace=frontend
```
- Testes manuais executados localmente (login, salvar vaga, clicar na
notificação, reload de página, reabertura do dropdown) confirmando:
  - Notificação local recém-criada é clicável imediatamente.
  - Após reload, a notificação (agora vinda da API) continua clicável.
- Ao clicar, o modal da vaga abre e a notificação passa a exibir o
indicador de lida, sem sumir da lista.

## Testes adicionados
- `tests/unit/new_dashboard/branch-coverage.test.tsx`: clique em
notificação com/sem `jobId` (navegação + marcação como lida, e
não-interação quando não há vaga associada), fallback de título de rota
não mapeada, fechamento de menus ao clicar fora, inclusão de
mensagem/notificação local via evento sem depender de reload, e reload
via API quando o evento não traz um item.
- `tests/unit/new_dashboard/api.test.ts`: chamada à rota `PATCH
/notifications/:id/read`, mapeamento de `entityType`/`entityId` para
`jobId` (só quando `entityType === "job"`) e de `readAt` para `isRead`.
- `tests/unit/new_dashboard/hooks.test.tsx`: cenários de erro combinado
ao carregar vagas salvas e recomendadas, ausência de busca inicial de
recomendações, atualização de status para vaga inexistente,
reaproveitamento de vaga já existente com o mesmo status (sem chamada
desnecessária de update), e mensagem de erro padrão quando a rejeição
não é uma instância de `Error`.
- Cobertura de branches do projeto: 78,35% → 80,1% (mínimo exigido: 80%,
conforme `CONTRIBUTING.md`).

## Riscos
- Notificações sem `entityType: "job"` (ex.: mensagens de
mentor/sistema) permanecem não clicáveis por design, já que não há vaga
associada para navegação. Não é uma regressão, é o comportamento
esperado dado o escopo da task (front-end).
- Nenhuma mudança de contrato de API foi necessária; o backend já
expunha os campos usados (`entityType`, `entityId`, `readAt`), então não
há risco de quebra em outros consumidores da API.
- O pipeline de CI aponta falha em `branch-coverage.test.tsx` e
`home.profile.test.tsx` (componente CareerChecklist, não tocado nesta
PR) por dependência de data não mockada nos testes: eles esperam o texto
fixo "julho de 2026", que deixou de bater assim que o mês virou para
agosto. A branch foi aberta em julho, quando os testes ainda passavam; a
falha não tem relação com as mudanças desta PR e reproduz da mesma forma
na `develop`. Recomendo abrir uma issue separada para mockar a data
nesses testes. Os dois testes foram excluídos apenas da execução local
usada para validar a cobertura (via filtro `-t`), sem nenhuma alteração
no código desses arquivos.

## Evidências
- Commits:
- `b8deaa7` — feat(frontend): allow user to click on local notifications
to navigate through job vacancies
- `158487f` — feat(frontend): make notifications click enable and mark
them as seen
## Resumo

Fecha a PAV-74 repensando a geração de keywords do scraper após o fim do
fluxo de submissão de keywords por usuário.

## O que foi feito

- Adicionado gerador de keywords no scraper Go, combinando cargo +
tecnologia por categoria compatível.
- Criado `generator.json` separado de `keywords.json` para versionar a
matriz de títulos, tecnologias e categorias.
- Mantido fallback interno no Go caso `generator.json` não esteja
disponível.
- Aplicado `GenerateSearchKeywords` no carregamento padrão, store,
scrape e cache key.
- Desabilitado o fluxo de submissão de keywords por usuário com
`KWSYNC_ENABLED=false`.
- `POST /keywords` agora retorna `403` quando o kwsync está desligado.
- `GET /keywords` permanece ativo.
- `publish` e `publishBatch` não publicam no Valkey quando
`KWSYNC_ENABLED=false`.
- Consumer Go do `kwsync` não inicia quando a flag está desligada.
- Documentada a flag em `.env.example` e `backend/.env.example`.
- Adicionados/atualizados testes de backend e scraper.

## Validação

- `npm test --workspace=backend`
- `go test ./...` dentro de `scraper-go`
- `git diff --check`
…e memória do scraper (#226)

- feat(scraper): configure global concurrency and runtime limits
- Add centralized SCRAPER_MAX_CONCURRENCY configuration with a safe
default of 12.
- Reject invalid environment values during startup instead of using a
silent fallback.
- Cap manual and scheduled executions before cache-key and pipeline
processing.
- Configure Go runtime, CPU, and memory limits for the scraper
container.
- Add structured logs, defensive validation, tests, and operational
documentation.

- Refs: PAV-118
## Contexto

O `package-lock.json` estava dessincronizado dos manifests do monorepo.
Em ambientes baseados em `node:24-alpine`, o `npm ci` falhava com:

```text
Missing: yaml@2.9.0 from lock file
```

A dependência `yaml@2.9.0` é requerida de forma transitiva e opcional
pelo `lint-staged@17.0.8`, mas sua entrada raiz estava ausente no
lockfile.

## Alteração

* Regenerado o `package-lock.json` com Node 24 e npm 11.
* Adicionada corretamente a resolução de `yaml@2.9.0`.
* Normalizados metadados de dependências opcionais de plataforma.
* Preservadas as versões declaradas nos manifests.
* Nenhum `package.json` foi alterado.
* Nenhuma dependência foi atualizada de forma indiscriminada.
* Nenhuma alteração funcional foi introduzida.

O diff está restrito a:

```text
package-lock.json | 79 alterações
16 inserções
63 remoções
```

## Validações

### Reprodutibilidade

* `npm ci` executado duas vezes em ambiente limpo com `node:24-alpine`.
* O hash do lockfile permaneceu estável após as duas instalações.
* `npm ci` também validado com Node 22 e npm 10, utilizados pelo CI.
* `yaml@2.9.0` foi resolvido corretamente.
* O `npm ci` não produziu novas alterações no lockfile.

### Testes e builds

* Backend: 60 arquivos e 569 testes aprovados.
* Frontend: testes aprovados.
* Frontend: lint aprovado sem erros e com warnings preexistentes.
* Frontend: build aprovado.
* Front Admin: 36 arquivos e 107 testes aprovados.
* Front Admin: build aprovado.
* Scraper Go: testes aprovados.

### Docker

Foram construídas com sucesso as imagens:

* `migrate`
* `backend`
* `frontend`
* `front_admin`

A stack integrada foi iniciada e validada:

* PostgreSQL saudável.
* Valkey saudável.
* Migration concluída com código de saída zero.
* Backend saudável em `http://localhost:3001/health`.
* Frontend disponível.
* Front Admin disponível.
* Painel administrativo de observabilidade funcional.

Também foi realizada uma coleta controlada para validar a PAV-118:

* Limite de aproximadamente 1,5 CPU aplicado ao scraper.
* Memória permaneceu abaixo do limite de 2 GiB.
* Nenhum reinício do scraper ou backend.
* Nenhum panic, fatal, timeout ou OOM identificado.
* 862 adapters concluídos e 8 falhas relacionadas a providers externos.
* Backend permaneceu saudável após a parada controlada do scraper.
* O painel identificou corretamente o scraper como indisponível após a
parada.

## Limitações conhecidas

O lint do `front_admin` ainda possui quatro erros preexistentes e não
relacionados ao lockfile. Seus testes e build foram aprovados. Esses
erros não foram corrigidos para preservar o escopo restrito desta
entrega.

O CI utiliza Node 22, enquanto os Dockerfiles utilizam `node:24-alpine`.
O lockfile foi validado nas duas versões, mas o alinhamento e a fixação
das versões de Node e npm serão tratados separadamente na evolução do
CI/CD.

## Checklist

* [x] Lockfile regenerado no ambiente oficial.
* [x] Nenhuma edição manual do lockfile.
* [x] `yaml@2.9.0` corretamente resolvido.
* [x] `npm ci` funciona em Linux/Alpine.
* [x] Duas instalações limpas produziram o mesmo resultado.
* [x] `npm ci` não modifica o lockfile.
* [x] Builds Docker aprovados.
* [x] Testes dos workspaces aprovados.
* [x] Diff restrito ao `package-lock.json`.
* [x] Nenhuma atualização ampla de dependências.
* [x] Nenhuma alteração funcional.

Fixes PAV-128
@RuhanFreitas
RuhanFreitas changed the base branch from master to develop August 21, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants