Adicionando o Unsplash como um segundo provedor de imagem de destaque
O blog-manager já tinha uma fonte de imagem pras imagens de destaque: o Pexels. Uma caixa de busca bate na API do Pexels, eu escolho uma foto, e a URL é hotlinkada no frontmatter do post. Um issue anterior transformou isso numa camada de verdade agnóstica de provedor (um registro indexado por nome de provedor, resultados intercalados em round-robin, um formato de hash normalizado compartilhado). Este aqui era sobre de fato plugar um segundo provedor nesse registro: o Unsplash.
A parte interessante não foi a integração de busca em si. As diretrizes de API do Unsplash têm dois requisitos que o Pexels não tem: você precisa hotlinkar (nada de baixar e re-hospedar, o que convenientemente combina com o jeito que esse app já trata o Pexels), e você precisa dar um ping num endpoint de “rastreamento de download” por foto sempre que alguém de fato escolhe uma foto, só pra que o Unsplash possa contar isso como um download nas próprias métricas deles.
O que eu construí
Unsplash::Client espelha Pexels::Client quase linha por linha: Net::HTTP feito à mão, um proc connection: como costura de teste em vez de puxar webmock ou VCR, a mesma hierarquia Error/AuthError sob ImageSearch::Error. A única diferença de verdade é o formato de resposta do Unsplash (results em vez do photos do Pexels, urls.regular/urls.small em vez de src.large/src.medium) e um novo método track_download pro ping de rastreamento.
O próprio ping virou seu próprio job pequeno, UnsplashDownloadPingJob, disparado a partir de select_image no momento em que alguém escolhe uma foto do Unsplash. Sem retries, sem discard_on, só um rescue-and-log. Se o endpoint de rastreamento do Unsplash estiver fora do ar, isso não é motivo pra bloquear alguém de escolher uma imagem de destaque.
Uma decisão que quase pulei
A URL de rastreamento de download (links.download_location) vem de volta na resposta de busca do Unsplash e viaja como um campo de formulário escondido através do botão “Select” já existente em cada resultado de busca. O que significa, por construção, que é um valor que o navegador controla. Minha primeira versão simplesmente pegava o que o client mandava e disparava um GET autenticado nele com a chave do Unsplash do app anexada. Um campo de formulário adulterado vazaria alegremente essa chave pra qualquer host que um atacante colocasse nesse campo.
O Brakeman não pegou isso, porque não é um padrão que ele checa, e é fácil de perder porque url_full (a URL real da imagem) tem exatamente a mesma forma de “confiar no cliente” mas é inofensivo, já que é o navegador que busca essa, não o servidor. O fix foi uma checagem de host de uma linha em track_download antes de anexar o header Client-ID. Pequeno, mas do tipo que é óbvio em retrospecto e invisível até você ir procurar.
O que me surpreendeu
Verificar isso manualmente sem uma chave real do Unsplash (registrar uma é uma viagem ao painel pra depois) virou um pequeno desvio. Acabei conduzindo o app através de ActionDispatch::Integration::Session dentro de um script rails runner contra o banco de dados de desenvolvimento real, já que o app está atrás de autenticação OIDC de verdade e não tem bypass de modo dev. Isso funcionou, até eu misturar um dispatch completo de request/response com uma query pura do ActiveRecord no mesmo processo e bater num erro genuinamente confuso de ActiveSupport::ExecutionContext nas profundezas do Rails. Nada a ver com meu código. Separar cada checagem na sua própria invocação de runner fez o erro sumir. Eu não tinha esbarrado nessa interação específica antes.
A única coisa que funcionou de forma limpa: o app já tinha uma chave real do Pexels configurada em desenvolvimento, então consegui confirmar que a mudança pro registro de dois provedores não regrediu a busca só-Pexels já existente. Ela retornou resultados reais do Pexels e o rodapé “Photos provided by Pexels,” exatamente como antes.
O que vem a seguir
Isso vai ao ar com o Unsplash conectado à busca, seleção, atribuição e página de configurações, mas não fica ativo até que uma Unsplash Access Key seja registrada e colada em Settings. O modo demo é 50 requisições por hora, o que o cache de busca de 12 horas já existente torna tranquilo pra uma pessoa só. A aprovação de produção (5.000 por hora) precisa de screenshots de atribuição enviados pro Unsplash depois que isso tiver sido de fato usado por um tempo.
Leitura relacionada
O bug da imagem de destaque que na verdade eram três bugs
Um card que mentia sobre um hero ausente, um caminho relativo resolvido contra a origem errada, e um bloqueio de hotlink do meu blog contra o meu próprio app.
Construindo imagens de destaque auto-hospedadas antes de eu ter qualquer coisa que precisasse delas
Um pipeline de download e re-hospedagem para imagens CC: vips de um buffer, um force-encode UTF-8 que corromperia o JPEG, e uma fila velha parecendo bug.
O que acontece quando um job faz broadcast para ninguém
Fechando o loop da imagem de destaque: frontmatter só por inserção, uma guarda que pegou divergência real na primeira execução, e um broadcast sem ouvinte.
Você também pode achar útil
RackNerd VPS
Hospedagem VPS econômica para serviços leves que funcionam continuamente.
Como afiliado da RackNerd, ganho com compras qualificadas.
Saiba maisProton VPN
VPN comercial com filtragem NetShield e interruptor de desligamento automático.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba maisProton Drive
Armazenamento em nuvem criptografado, da equipe por trás do Proton Mail.
Como parceiro da Proton, ganho com compras qualificadas dos serviços de privacidade e segurança da Proton (Pass, Mail, VPN, Drive).
Saiba mais