在博客和 Sitio Astro 上集成多个 API:Dev.to 和 Hashnode

如果是的话,可能会在不同的博客平台上写入。在这种情况下,我们可以将 Hashnode 应用于不同的受众。但是,您是否可以在个人情况下发帖?您可以将 ambas API 集成到 Astro 的产品组合中。

艾尔·德萨菲奥

主要回忆录:

  • 获取不同 API 的帖子
  • 统一数据格式
  • 顺序纪年法
  • Manejar 错误率限制
  • 使用 TypeScript 确保安全
  • 初始配置

    Primero、有关我们需要的信息的接口定义:

    interface BlogPost {
      title: string;
      brief: string;
      slug: string;
      dateAdded: string;
      rawDate: string;
      coverImage: string;
      url: string;
      source: string;
    }
    
    interface HashnodeEdge {
      node: {
        title: string;
        brief: string;
        slug: string;
        dateAdded: string;
        coverImage?: {
          url: string;
        };
        url: string;
      };
    }

    整合开发

    La API de Dev.to es RESTful y bastante directa。 Aquí está cómo la Implementé:

    async function getDevToPosts() {
      try {
        const params = new URLSearchParams({
          username: 'tuUsuario',
          per_page: '20',
          state: 'all',
          sort: 'published_at',
          order: 'desc'
        });
    
        const headers = {
          'Accept': 'application/vnd.forem.api-v1+json'
        };
    
        // Agregar API key si está disponible
        if (import.meta.env.DEV_TO_API_KEY) {
          headers['api-key'] = import.meta.env.DEV_TO_API_KEY;
        }
    
        const response = await fetch(`https://dev.to/api/articles?${params}`, { headers });
    
        if (!response.ok) {
          throw new Error(`HTTP ${response.status}`);
        }
    
        const posts = await response.json();
    
        return posts.map((post: any) => ({
          title: post.title,
          brief: post.description,
          slug: post.slug,
          dateAdded: formatDate(post.published_timestamp),
          rawDate: post.published_timestamp,
          coverImage: post.cover_image || '/images/default-post.png',
          url: post.url,
          source: 'devto'
        }));
      } catch (error) {
        console.error('Error al obtener posts de Dev.to:', error);
        return [];
      }
    }

    整合 Hashnode

    使用 GraphQL 的 Hashnode,需要不同的配置:

    async function getHashnodePosts() {
      try {
        const query = `
          query {
            publication(host: "tuBlog.hashnode.dev") {
              posts(first: 20) {
                edges {
                  node {
                    title
                    brief
                    slug
                    dateAdded: publishedAt
                    coverImage {
                      url
                    }
                    url
                  }
                }
              }
            }
          }
        `;
    
        const response = await fetch('https://gql.hashnode.com', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ query })
        });
    
        const { data } = await response.json();
    
        return data.publication.posts.edges.map((edge: HashnodeEdge) => ({
          title: edge.node.title,
          brief: edge.node.brief,
          slug: edge.node.slug,
          dateAdded: formatDate(edge.node.dateAdded),
          rawDate: edge.node.dateAdded,
          coverImage: edge.node.coverImage?.url || '/images/default-post.png',
          url: edge.node.url,
          source: 'hashnode'
        }));
      } catch (error) {
        console.error('Error al obtener posts de Hashnode:', error);
        return [];
      }
    }

    合并结果

    La Magia Ocurre al Combinar y Ordenar los posts:

    const hashnodePosts = await getHashnodePosts();
    const devtoPosts = await getDevToPosts();
    
    const allBlogPosts = [...hashnodePosts, ...devtoPosts]
      .sort((a, b) => new Date(b.rawDate).getTime() - new Date(a.rawDate).getTime());

    错误率限制

    为了限制错误率,实施以下策略:

    客户端拉出的缓存:

    const CACHE_DURATION = 5 * 60 * 1000; // 5 minutos
    let postsCache = {
      data: null,
      timestamp: 0
    };
    
    async function getAllPosts() {
      const now = Date.now();
      if (postsCache.data && (now - postsCache.timestamp) < CACHE_DURATION) {
        return postsCache.data;
      }
    
      // Obtener y combinar posts...
    
      postsCache = {
        data: allBlogPosts,
        timestamp: now
      };
      return allBlogPosts;
    }

    回退指数的保留:

    async function fetchWithRetry(url: string, options: any, retries = 3) {
      for (let i = 0; i < retries; i++) {
        try {
          const response = await fetch(url, options);
          if (response.status === 429) { // Rate limit
            const retryAfter = response.headers.get('Retry-After') || '60';
            await new Promise(resolve => setTimeout(resolve, parseInt(retryAfter) * 1000));
            continue;
          }
          return response;
        } catch (error) {
          if (i === retries - 1) throw error;
          await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
        }
      }
    }

    Astro 渲染

    最后,渲染 los 帖子 en nuestro 组件 Astro:

    ---
    const allBlogPosts = await getAllPosts();
    ---
    
    
    {allBlogPosts.map((post) => (
    {post.title}
    {post.source === 'devto' ? 'Dev.to' : 'Hashnode'}

    {post.title}

    {post.brief}

    Leer más
    ))}

    未经允许的整合:

  • Mantener una única fuente de verdad para nuestros posts
  • 多个平台的内容
  • 优雅的管理错误
  • 请注意安全和安全
  • 该代码已在 GitHub 上完成。

    ¿您所在的地方有集成其他博客平台吗? ¡比较经验和评论! 👇

    webdev #astro #typescript #api