# Blueprint 10 — Ntfy Notifications - **ID:** BP-10 - **Feature:** Notificações via ntfy sobre sucesso/falha de jobs - **Requisitos:** RF-017 - **Depende de:** BP-05 (Worker Pool) - **Status:** pendente ## Objetivo Implementar um cliente HTTP que envia notificações para um servidor [ntfy](https://ntfy.sh) (self-hosted ou público) ao final de cada job, informando se a conversão foi concluída com sucesso ou falhou. Respeitar `DRY_RUN`. ## Contexto - BP-05 definiu o worker pool com um fluxo de `process` que termina chamando `finishJob` (status `done` ou `error`). O notificador será chamado antes do finish. - `config.Service` fornecerá `NtfyEnabled`, `NtfyURL`, `NtfyTopic`, `NtfyToken`. - O padrão segue a mesma abordagem do `Rescanner`: interface injetada no `worker.Pool`, substituível para testes. ## Restrições e Convenções - Sem comentários; erros com `%w`; logs com `slog.With("job_id", id)`. - **Resiliência:** falha ao enviar notificação **não** deve interromper o pipeline. Logar o erro e seguir. - `DRY_RUN=true`: **não** enviar notificação; logar a intenção. - Se `NTFY_ENABLED=false` ou `NtfyURL`/`NtfyTopic` vazios: pular (log debug). - Token (`NTFY_TOKEN`) é opcional; se presente, enviar como `Authorization: Bearer `. - Timeout de 10s no HTTP client. - Mensagens em inglês. ## Design ### `internal/ntfy/client.go` ```go type Client struct { cfg *config.Service hc *http.Client } func New(cfg *config.Service) *Client // Notify envia uma notificação com o resultado do job. // Implementa worker.Notifier. func (c *Client) Notify(ctx context.Context, job *models.Job) error ``` - Endpoint: `POST {NtfyURL}/{NtfyTopic}` - Prioridade: `high` para erro, `default` para sucesso. - Tags: `white_check_mark` (done), `x` (error). - Corpo: `json.Marshal(ntfyMessage{...})` com title, message, priority, tags. - Header `Authorization: Bearer ` apenas se token != "". ### `internal/worker/notifier.go` ```go type Notifier interface { Notify(ctx context.Context, job *models.Job) error } type NoopNotifier struct{} func (NoopNotifier) Notify(ctx context.Context, job *models.Job) error { return nil } ``` Adicionar campo `Notifier` ao `worker.Pool` e chamar `p.Notifier.Notify(ctx, job)` antes de `finishJob`. ### Wiring em `cmd/server/main.go` - `notifier := ntfy.New(cfgSvc)` (ou `NoopNotifier{}` se `NtfyEnabled=false`). - Passar `notifier` ao `worker.New(..., notifier)`. ### Config (novas chaves editáveis) | Key | Default | Descrição | |-----|---------|-----------| | `NTFY_ENABLED` | `false` | Habilita notificações ntfy | | `NTFY_URL` | — | URL base do servidor ntfy | | `NTFY_TOPIC` | — | Tópico para publicar | | `NTFY_TOKEN` | — | Token de autenticação (opcional) | ## Tarefas - [ ] Adicionar `Notifier` interface em `internal/worker/`. - [ ] Criar `internal/ntfy/client.go` (`New`, `Notify`). - [ ] Adicionar keys ntfy ao `config` (Key + validação + editableKeys + Config struct + buildConfig). - [ ] Chamar `Notifier.Notify` no `worker.process` antes de `finishJob`. - [ ] Wiring no `main.go`: `ntfy.New(cfgSvc)` → `worker.New(...)`. - [ ] Atualizar documentos: `README.md`, `AGENTS.md`, `requirements.md`, `architecture.md`. ## Testes `internal/ntfy/client_test.go` (httptest.Server): - [ ] `Notify` envia `POST /` com prioridade/título/tags corretos para job concluído. - [ ] `Notify` envia prioridade `high` e tag `x` para job com erro. - [ ] `DRY_RUN=true` → nenhum request é feito. - [ ] `NtfyEnabled=false` → nenhum request, retorna nil. - [ ] Token presente → header `Authorization: Bearer `. - [ ] Timeout/erro de rede → erro logado, não interrompe. `internal/worker/worker_test.go` (atualizar): - [ ] Verificar que `Notifier` fake é chamado antes do `finishJob` tanto em sucesso quanto em erro. - [ ] Verificar que `NoopNotifier` não causa efeitos colaterais. ## Definição de Pronto - DoD comum atendido. - `go test ./internal/ntfy/... ./internal/worker/...` verde. - `make run-prod`: notificações enviadas ao final de cada job (se habilitado). ## Arquivos - **Criar:** `internal/ntfy/client.go` (+ `_test.go`), `internal/worker/notifier.go`. - **Modificar:** `internal/config/config.go`, `internal/worker/worker.go`, `cmd/server/main.go`.