O fail-closed silencioso da S3 deixava a assessoria cega: token errado não deixava rastro além do picker vazio. Fim da caixa-preta: - `instagramSyncStatus` (jsonb hidden) na global `social-feed-settings`, escrito pelo render, pelo afterChange (sync awaited quando a credencial IG muda) e pela rota `POST /api/social-feed/sync` (botão, auth admin + same-origin). - `InstagramApiError` + `describeInstagramError`: o fetch parseia o body de erro da Graph API e o mapeia para copy pt-BR com a correção (OAuthException/expired → "token inválido/expirado ou emitido via Facebook Login — gere pelo Instagram Login"; "Invalid user id" → confira o ID; 5xx → indisponível; TypeError → rede; AbortError → demorou demais). O ID só casa com "user" E "id" juntos (token rejections real falam "the user must be logged in" — não podem virar a branch de ID). - `InstagramSyncStatusPanel` (campo ui irmão do picker): 5 estados fiéis ao rascunho; gate respeita o master `enabled`; `outcome` transiente limpo ao editar credencial; aria-live nos boxes interativos. Home inalterada (fail-closed intacto). - Bug real: `afterChange` roda dentro da transação do save (row lock até os hooks retornarem); persistir o status via pool numa 2ª conexão deadlockava no lock da própria linha (~80-230s/save). Fix: persists do sync-hook na MESMA transação via `getPostgresTransactionDatabase` (C6); render/rota sem transação seguem no pool. Diagrama: UPDATE transactionid Lock vs idle in transaction; fix verificado 45ms vs 232s. - Stub IG ganha estado `invalid-token`; runbook docs/ops/instagram-feed-token-runbook.md; migration add_instagram_sync_status (jsonb nullable). - Débitos registrados: #124 (janela de lock) e #125 (polish P3). - Gates: unit 2463 + int 791; lint/format/tsc/cycles; build; e2e admin 5/5 + frontend serial 19/19.
4.1 KiB
Runbook — Feed do Instagram: validar/gerar o token (S11)
Issue: #115 · Entrega: S11 (painel de status da sincronização no admin)
O que o painel mostra
Na global Configurações → Feed de redes sociais, o bloco "Instagram — estado da sincronização" diz o estado da última tentativa:
| Estado | Significado | Ação |
|---|---|---|
| Sincronizado · há X min · N posts | Credenciais aceitas; posts no board "Acompanhe de perto" | Nenhuma |
| Falha na última sincronização | A Graph API recusou a última tentativa; o texto diz o motivo e a correção | Corrigir conforme o motivo e clicar Tentar sincronizar de novo |
| Instagram ainda não configurado | Falta token ou ID | Preencher os dois campos |
| Aguardando a primeira sincronização | Configurado, nenhuma tentativa ainda | Clicar Tentar sincronizar de novo |
O estado é atualizado no salvar (quando as credenciais mudam), em cada render da home (cache de 5 min) e no clique do botão. A home pública nunca quebra quando a API falha — o board segue com artigos + YouTube.
Diagnóstico da ocorrência de 2026-08-19 (causa provável)
Produção mostrava picker vazio e nenhum card IG com token + ID preenchidos — o fail-closed silencioso da S3 não dizia o porquê. A causa mais provável, na ordem:
- Token emitido via Facebook Login (page token) — a
graph.instagram.comrecusa; o refresh automático não renova (o endpointrefresh_access_tokensó aceita tokens do Instagram Login). - Token expirado/revogado.
- ID do usuário errado (não é o da conta Business/Creator vinculada).
Confirmação em 1 clique: no admin, Tentar sincronizar de novo — o painel mostra o motivo exato devolvido pela API. Se a causa for a 1, aparece a mensagem de token recusado com a correção; se for a 3, a mensagem de ID não reconhecido.
Validar um token sem o admin
# O endpoint /me responde com o username e o tipo de conta se o token é válido
# para a Graph API do Instagram (Business/Creator):
curl -s "https://graph.instagram.com/me?fields=user_id,username&access_token=SEU_TOKEN"
# Teste o refresh (só tokens emitidos pelo Instagram Login passam):
curl -s "https://graph.instagram.com/refresh_access_token?grant_type=ig_refresh_token&access_token=SEU_TOKEN"
/mecomOAuthException→ token inválido/expirado ou emitido via Facebook Login (a Graph API do Instagram só aceita tokens do Instagram Login)./meok mas o ID configurado não bate comuser_id→ corrigir o campo "ID do usuário".
Gerar um token de longa duração (Instagram Login)
- Acessar
developers.facebook.comcom a conta que administra a página → criar/usar um app do tipo Business (qualquer tipo serve para o fluxo de negócios, mas o app precisa estar em modo Live para uso fora do sandbox). - Produto Instagram → Instagram Login with Basic Display → Gerar token com a conta Business/Creator do @depjorgesolla.
- Trocar o token de curta duração pelo de longa duração usando o
Long-Lived Token Exchange da documentação do Instagram Basic Display
(ou usar o endpoint
refresh_access_tokendo passo anterior após a troca). - Colar o token e o ID numérico da conta no admin e clicar Tentar sincronizar de novo.
O que NÃO fazer
- Não usar token da API Graph do Facebook (página) — o app recusa.
- Não colar o token em arquivos, Issues ou chat — o campo é admin-only e deve continuar.
- Não zerar o token para "testar" — o painel de status dispensa tentativa-e-erro.