node.js - 使用 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,并保存包含新文章url
和publicId
.
我的目标是调用saveArticles
then 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
查询在突变完成之前完成。
也许我提供的解决方案设计过度,或者有一种更简单的方法来解决这个问题 - 我对编写后端有点陌生,所以请原谅任何不好的代码,我将非常感谢你的建议,因为它会有所帮助作为一名开发人员,我成长得更多。
亲切的问候,
阿什拉夫·埃尔穆希布
解决方案
推荐阅读
- acumatica - Acumatica - 将仪表板参数作为查询参数传递
- java - 在struts2项目的eclipse控制台上禁用无用的[freemark]日志
- c++ - 如何在不闪烁的情况下为 ESP32 运行单元测试?
- javascript - 单击路由不起作用,但在重新加载 URL 时它起作用
- dictionary - Deferencing 类型映射
- android-studio - Android Studio 3.5.1 中的软包装编辑器
- react-native - 是否可以自定义 react-native-media-controls 样式?
- tastypie - 如何使用一个 URI 获取多个资源?
- c# - Crystal Reports - 连接到另一个项目中的数据源
- ibm-watson - IBM watson 助手:将 IBM watson 聊天机器人与 android API 17 集成