Agregar búsqueda de imágenes de Pexels a mi blog manager
Medium quiere una imagen de encabezado. Mi blog manager necesitaba una forma de elegir una sin salir de la app.
He estado construyendo una app en Rails que me ayuda a sindicar posts de mi blog en Astro hacia Medium y LinkedIn. El flujo de cross-posting ya estaba casi terminado; podía escanear un repositorio de GitHub en busca de archivos markdown, renderizarlos como HTML, y subirlos a Medium como borradores. Pero faltaba una pieza: Medium destaca una imagen de encabezado en la parte superior de cada post, y mi app no tenía forma de seleccionar una.
La elección obvia fue Pexels. Tienen una API limpia, un nivel gratuito generoso, y un requisito de atribución fácil de cumplir con un <figcaption>.
Lo que construí
La sección de Pexels vive en la página de detalle del post. Hay un formulario de búsqueda prellenado con el título del post, una grilla de resultados que carga sin recargar la página completa, y un botón “Select” por imagen. Al elegir una, la tarjeta del post se actualiza en el momento y el botón de importación a Medium se activa.
El enfoque de Turbo Frame hizo esto directo. El formulario de búsqueda apunta a un turbo_frame_tag "pexels_results", así que la respuesta reemplaza solo esa región de la 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" %>
Del lado del controlador, search_pexels renderiza el partial directamente dentro del frame. Rápido, sin transición de página, sin reseteo de scroll.
La particularidad de autenticación de Pexels
Todas las demás APIs que he integrado usan Authorization: Bearer {token}. Pexels no. Su header de autenticación es simplemente la API key sin adornos:
req["Authorization"] = @api_key # correct
req["Authorization"] = "Bearer #{@api_key}" # wrong - 401
Está documentado, pero es fácil pasarlo por alto al copiar de otro cliente.
Dónde guardar la API key
Por blog vs. global: tengo una sola cuenta de Pexels, así que hacerlo por blog significaría ingresar la misma key varias veces. En su lugar, agregué un modelo singleton AppSetting:
class AppSetting < ApplicationRecord
encrypts :pexels_api_key
def self.current
first_or_create!
end
end
Active Record Encryption (ya usado para los tokens de GitHub y Medium) maneja el cifrado en reposo. La página de configuración en /settings me da una UI para pegar la key sin tocar credentials.yml.enc.
Bloqueando la importación a Medium
El botón de importación a Medium ahora requiere que haya una imagen de Pexels seleccionada. La verificación vive tanto en la vista (tooltip deshabilitado) como en el controlador (redirect con alert); los bloqueos que solo existen en la vista se pueden saltear con un POST directo.
Cuando corre DraftCreator
DraftCreator antepone la imagen seleccionada como un <figure> antes del cuerpo HTML del post. El <figcaption> incluye la atribución del fotógrafo, cumpliendo los requisitos de Pexels. Medium lo renderiza en la parte superior del borrador.
Una trampa en los tests
minitest/mock se eliminó en Minitest 6.0.6; Minitest::Mock ya no existe. Descarté el test de controlador basado en stubs y me apoyé directamente en los tests unitarios de Pexels::Client.
Qué sigue
La acción search_pexels todavía lee de Rails.application.credentials; conectarla a AppSetting.current.pexels_api_key es el seguimiento obvio de una línea. Después de eso: cross-posting a LinkedIn, que ya tiene el token pero no el job.
Lecturas relacionadas
Qué pasa cuando un job transmite y nadie escucha
Cerrando el ciclo de la imagen destacada: frontmatter que solo inserta, una validación que detectó drift real, y un broadcast sin oyentes.
Una corrección de desfase de documentación que no fue tan aburrida como sonaba
Tres puntos de auditoría que se volvieron algo más: una afirmación a medio corregir, un restablecimiento de contraseña muerto en silencio, y un correo de staging que enlazaba a producción.
Un 500 escondido dentro de las rutas aisladas de un engine montado
El dashboard de jobs devolvía un 500 en vez de una página de login: los route helpers sin calificar se resuelven contra el engine, no contra la app. Una línea, más su gemela dormida.
También te podría ser útil
AdGuard para iOS
Bloqueo de anuncios y rastreadores en todo el sistema en iOS, sin necesidad de un servidor DNS aparte.
Como afiliado de AdGuard, obtengo ingresos por las compras que califican.
Más informaciónProton Mail
Correo electrónico cifrado de extremo a extremo, con arquitectura de acceso cero.
Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).
Más informaciónProton Drive
Almacenamiento en la nube cifrado, del equipo detrás de Proton Mail.
Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).
Más información