首页 > 解决方案 > 使用 GraphQL 和 Apollo-client 改变来自 API 的数据以使用 Cloudinary 图像

问题描述

我一直在构建Newsly - 一个从News API获取文章并在 Next.js 应用程序中显示结果的开源新闻平台。

Newsly 还使用 GraphQL API 来改变来自News API的数据。

现在这里是来自News API的原始响应对象,其中包含一系列文章 - 在这种情况下,只有一篇文章可以让您了解我们正在使用的数据:

{
  "articles": [
    {
      "source": {
        "id": null,
        "name": "BBC News"
      },
      "author": "https://www.facebook.com/bbcnews",
      "title": "Migrants reach Spain's Ceuta enclave in record numbers - BBC News",
      "description": "At least 2,700 people arrive in Ceuta from Morocco, with some swimming in using rubber rings.",
      "url": "https://www.bbc.com/news/world-europe-57150051",
      "urlToImage": "https://ichef.bbci.co.uk/news/1024/branded_news/FB26/production/_118549246_mediaitem118549245.jpg",
      "publishedAt": "2021-05-17T20:03:50Z",
      "content": "media captionSome of the migrants swam to Ceuta using inflatable rings\r\nAt least 2,700 migrants have reached Spain's Ceuta enclave from neighbouring Morocco, a record number of arrivals in a single d… [+1897 chars]"
    },
  ]
}

现在问题来了。出于各种原因,我试图返回带有Cloudinary URL 的文章,而不是urlToImage来自 News API 的普通文章。当您调用 Cloudinary 并为其提供特定 URL 时,它会返回新的 URL 并将新图像保存到您的 Cloudinary 帐户。

//simple example
const { url } = await cloudinary.v2.uploader.upload(urlToImage);
//url is the new one, urlToImage is the one coming from the API

看到这里,你可能会认为我可以简单地用 newurl而不是 old 来返回文章urlToImage。但是,每次用户访问Newsly时,文章都是通过 Next.js 获取的,这基本上意味着每次用户进入网站时,Cloudinary 都无法意识到它已经从 News API 中保存了一些图像,而我的帐户将被充满了许多重复的图像。

Cloudinary 返回 apublic_id url,然后我们可以使用它来帮助 Cloudinary 识别图像并避免两次上传它们。那是我的想法。

经过思考,我意识到我需要一个数据库来存储带有 Cloudinary 图像的新文章。我选择了 MongoDB,因为它更容易使用,因为我已经有使用 NoSQL 的经验。说得够多了,下面是Newsly API项目中的一些文件。

MongoDB模型:

//schema
const articleSchema = new Schema({
  publicId: {
    type: String,
    default: null,
  },
  source: {
    id: String,
    name: String,
  },
  author: String,
  title: String,
  description: String,
  url: String,
  urlToImage: String,
  publishedAt: String,
  content: String,
});

//model
const Article = model('Article', articleSchema);

GraphQL 类型定义:

  # query
  type Query {
    articles: [Article]
  }

  # types
  type Source {
    id: String
    name: String
  }

  type Article {
    _id: ID!
    publicId: ID
    source: Source
    author: String
    title: String
    description: String
    url: String
    urlToImage: String
    publishedAt: String
    content: String
  }

  # Mutations
  type Mutation {
    updateArticles: [Article!]!
    saveArticles: [Article!]!
  }

GraphQL 解析器:

//resolvers
export const resolvers = {
  Query: {
    articles: async () => {
      //getting articles from db
      const savedArticles: ArticleType[] = await Article.find({});
      return savedArticles;
    },
  },
  Mutation: {
    saveArticles: async () => {
      //fetching raw articles
      const {
        data: { articles },
      } = await newsAPI.get('/top-headlines', {
        params: {
          category: 'general',
          country: 'us',
          apiKey: process.env.NEWS_API_KEY,
          pageSize: 11,
        },
      });

      const savedArticles: ArticleType[] = await Article.find({});
      //deleting old articles
      await Article.deleteMany({});

      //initialize cloudinary
      cloudinary.v2.config({
        cloud_name: process.env.CLOUDINARY_NAME,
        api_key: process.env.CLOUDINARY_API_KEY,
        api_secret: process.env.CLOUDINARY_API_SECRET,
      });

      //deleting old cloudinary images
      savedArticles.forEach(async ({ publicId }) => {
        if (publicId) {
          await cloudinary.v2.uploader.destroy(publicId);
        }
      });

      //saving new articles
      articles.forEach(async (article: ArticleType) => {
        if (article.urlToImage) {
          await Article.create(article);
        }
      });
      return savedArticles;
    },
    updateArticles: async () => {
      //getting articles from db
      const savedArticles: ArticleType[] = await Article.find({});

      //initialize cloudinary
      cloudinary.v2.config({
        cloud_name: process.env.CLOUDINARY_NAME,
        api_key: process.env.CLOUDINARY_API_KEY,
        api_secret: process.env.CLOUDINARY_API_SECRET,
      });

      //updating articles to use cloudinary images
      savedArticles.forEach(async article => {
        const { urlToImage, publicId, _id } = article;

        if (publicId === null) {
          const { url, public_id } = await cloudinary.v2.uploader.upload(
            urlToImage
          );
          await Article.updateOne(
            {
              _id,
            },
            {
              publicId: public_id,
              urlToImage: url,
            }
          );
        }
      });

      return savedArticles;
    },
  },
};

关于突变:

saveArticles从 NewsAPI 获取原始文章,删除数据库中的旧文章并销毁我的 Cloudinary 帐户中存在的旧图像。然后,它将新文章保存到publicId最初为 null 的数据库中(请参阅 MongoDB 模式)。

updateArticles从数据库中获取保存的文章,将图片上传到 Cloudinary,并保存包含新文章urlpublicId.

我的目标是调用saveArticlesthen updateArticles,最后进行查询articles以从数据库中获取最终结果。最终结果示例:

{
  "_id": "60a2904b6cf44607acfbc8f8",
  "publicId": "lzqodhhxvvg2c18sxczv",
  "source": {
    "id": "the-washington-post",
    "name": "The Washington Post"
  },
  "author": "Jeff Stein",
  "title": "Child cash benefit will begin hitting millions of parents’ bank accounts July 15 - The Washington Post",
  "description": "Administration officials say 88 percent of all American children are slated to receive new monthly payments, with no action needed.",
  "url": "https://www.washingtonpost.com/us-policy/2021/05/17/biden-child-tax-benefit/",
  "urlToImage": "http://res.cloudinary.com/achraf-dev/image/upload/v1621266523/lzqodhhxvvg2c18sxczv.jpg",
  "publishedAt": "2021-05-17T14:31:00Z",
  "content": "Calculate how much you would get from the expanded child tax credit\r\nBiden administration officials estimate that households representing more than 65 million children -- or 88 percent of all U.S. ki… [+4905 chars]"
}

在我尝试从 Next.js 客户端查询和改变数据之前,一切似乎都很好:

//get server side props
export const getServerSideProps: GetServerSideProps = async () => {
  //saving articles
  await client.mutate({
    mutation: saveArticlesMutation,
  });
  //updating articles
  await client.mutate({
    mutation: updateArticlesMutation,
  });
  //fetching articles
  const {
    data: { articles },
  } = await client.query({
    query: articlesQuery,
  });
  //getting featured article
  const featuredArticle = findFeaturedArticle(articles);
  return {
    props: {
      featuredArticle,
      articles: articles.filter(
        (article: Article) => article !== featuredArticle && article.content
      ),
    },
  };
};

这实际上不起作用,因为articles查询在突变完成之前完成。

也许我提供的解决方案设计过度,或者有一种更简单的方法来解决这个问题 - 我对编写后端有点陌生,所以请原谅任何不好的代码,我将非常感谢你的建议,因为它会有所帮助作为一名开发人员,我成长得更多。

亲切的问候,

阿什拉夫·埃尔穆希布

标签: node.jstypescriptgraphqlnext.jsapollo-client

解决方案


推荐阅读