Adicionando busca de imagens do Pexels ao meu gerenciador de blog
O Medium quer uma imagem de cabeçalho. Meu gerenciador de blog precisava de uma forma de escolher uma sem sair do app.
Venho construindo um app em Rails que me ajuda a sindicar posts do meu blog em Astro pro Medium e pro LinkedIn. O fluxo de cross-posting já estava quase pronto; eu conseguia escanear um repositório do GitHub em busca de arquivos markdown, renderizá-los como HTML, e enviá-los ao Medium como rascunhos. Mas faltava uma peça: o Medium destaca uma imagem de cabeçalho no topo de todo post, e meu app não tinha como selecionar uma.
A escolha óbvia foi o Pexels. Eles têm uma API limpa, um plano gratuito generoso, e uma exigência de atribuição fácil de satisfazer com um <figcaption>.
O que eu construí
A seção do Pexels fica na página de detalhe do post. Há um formulário de busca pré-preenchido com o título do post, uma grade de resultados que carrega sem recarregar a página inteira, e um botão “Selecionar” por imagem. Quando você escolhe uma, o card do post atualiza no lugar e o botão de import pro Medium ativa.
A abordagem com Turbo Frame deixou isso direto. O formulário de busca mira num turbo_frame_tag "pexels_results", então a resposta substitui só aquela região da página:
<%= form_with url: search_pexels_post_path(post), method: :get,
data: { turbo_frame: "pexels_results" } do |f| %>
<%= f.text_field :query, value: post.title.presence || post.slug %>
<%= f.submit "Search" %>
<% end %>
<%= turbo_frame_tag "pexels_results" %>
No lado do controller, search_pexels renderiza o partial direto dentro do frame. Rápido, sem transição de página, sem reset de scroll.
A pegadinha de autenticação do Pexels
Toda outra API que integrei usa Authorization: Bearer {token}. O Pexels não. O cabeçalho de autenticação deles é só a chave de API crua:
req["Authorization"] = @api_key # correct
req["Authorization"] = "Bearer #{@api_key}" # wrong - 401
Está documentado, mas é fácil passar batido quando você copia de outro client.
Onde guardar a chave de API
Por blog vs. global: eu tenho uma conta só no Pexels, então por blog significaria digitar a mesma chave várias vezes. Adicionei um model singleton AppSetting no lugar:
class AppSetting < ApplicationRecord
encrypts :pexels_api_key
def self.current
first_or_create!
end
end
O Active Record Encryption (já usado pros tokens do GitHub e do Medium) cuida da criptografia em repouso. A página de configurações em /settings me dá uma UI pra colar a chave sem tocar no credentials.yml.enc.
Bloqueando o import pro Medium
O botão de import pro Medium agora exige que uma imagem do Pexels esteja selecionada. A checagem vive tanto na view (tooltip desabilitado) quanto no controller (redirect com alerta); bloqueios só de view são contornados com um POST direto.
Quando o DraftCreator roda
O DraftCreator antepõe a imagem selecionada como um <figure> antes do corpo HTML do post. O <figcaption> inclui a atribuição do fotógrafo, satisfazendo as exigências do Pexels. O Medium renderiza isso no topo do rascunho.
Uma pegadinha de teste
O minitest/mock foi removido no Minitest 6.0.6; Minitest::Mock não existe mais. Descartei o teste de controller baseado em stub e passei a depender só dos testes unitários de Pexels::Client diretamente.
O que vem a seguir
A ação search_pexels ainda lê de Rails.application.credentials; conectá-la a AppSetting.current.pexels_api_key é o desdobramento óbvio de uma linha só. Depois disso: cross-posting pro LinkedIn, que já tem o token mas não tem o job.
Leitura relacionada
Uma correção de desvio de documentação que não foi tão chata quanto parecia
Três itens da auditoria que cada um virou outra coisa: uma alegação meio corrigida, uma recuperação de senha silenciosamente morta, e um e-mail de staging que apontaria para produção.
Um 500 escondido dentro das rotas isoladas de uma engine montada
O painel de jobs retornava um 500 em vez de uma página de login: helpers de rota sem qualificação resolvem contra a engine, não contra a aplicação. Uma linha, mais o gêmeo dormente dela.
Apagando código morto, e pegando um motivo errado para uma resposta certa
Uma issue de limpeza com uma justificativa errada, uma varredura de documentação que não era necessária, e a credencial órfã que uma revisão pegou.
Você também pode achar útil
AdGuard para iOS
Bloqueio de anúncios e rastreadores em todo o sistema no iOS, sem necessidade de um servidor DNS separado.
Como afiliado da AdGuard, ganho com compras qualificadas.
Saiba maisProton Mail
E-mail criptografado de ponta a ponta, com arquitetura de acesso zero.
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