翻译 | 《JavaScript Everywhere》第8章 用户操作(^_^)

翻译 | 《JavaScript Everywhere》第8章 用户操作(_

写在最前面

大家好呀,我是毛小悠,是一位前端开发工程师。正在翻译一本英文技术书籍。

为了提高大家的阅读体验,对语句的结构和内容略有调整。如果发现本文中有存在瑕疵的地方,或者你有任何意见或者建议,可以在评论区留言,或者加我的微信:code_maomao,欢迎相互沟通交流学习。

(σ゚∀゚)σ…:*☆哎哟不错哦

第8章 用户操作

想像你刚刚加入了一个俱乐部(还记得“超级酷人秘密俱乐部”吗?),当你第一次出现时,没有任何事可做。俱乐部是一个空旷的大房间,人们进进出出,无法与俱乐部的其他人产生互动。我有点内向,所以这听起来还不错,但是我不愿意为此支付会员费。

现在,我们的API本质上是一个庞大的无用俱乐部。我们有创建数据的方法和用户登录的方法,但是没有任何一种方法允许用户拥有该数据。在本章中,我们将通过添加用户交互来解决这个问题。我们将编写代码,使用户能够拥有自己创建的笔记,限制可以删除或修改笔记的人员,并允许用户“收藏”他们喜欢的笔记。此外,我们将使API用户能够进行嵌套查询,从而使我们的UI可以编写将用户与笔记相关联的简单查询。

开始之前

在本章中,我们将对Notes文件进行一些相当重要的更改。由于我们数据库中的数据量很少,可能你会发现从本地数据库中删除现有笔记更容易。这不是必需的,但是可以减少你在阅读本章时的困惑。

为此,我们将进入MongoDB shell,确保引用了notedly的数据库(数据库名称在.env文件中),并使用MongoDB的 .remove()方法。

在终端中,键入以下内容:

$ mongo
$ use notedly
$ db.notes.remove({
    
    }) 

用户添加新笔记

在上一章中,我们更新了src/index.js文件,以便当用户发出请求时检查JWT。如果token令牌存在,我们将对其进行解码并将当前用户添加到我们的GraphQL上下文中。这使我们可以将用户信息发送到我们调用的每个解析器函数。我们将更新现有的GraphQL请求以验证用户信息。为此,我们将利用Apollo ServerAuthenticationErrorForbiddenError方法,这将允许我们引发适量的错误。这些将帮助我们调试开发过程以及向客户端发送适和的响应。

在开始之前,我们需要将mongoose包导入我们的mutations.js解析器文件中。这将使我们能够适当地分配交叉引用的MongoDB对象ID字段。更新src/resolvers/mutation.js顶部的模块导入,如下所示:

const mongoose = require('mongoose');

现在,在我们的newNote请求中,我们将用户设置为函数参数,然后检查是否将用户传递给函数。

如果找不到用户ID,我们将抛出AuthenticationError,因为必须登录到我们的服务才能创建新笔记。确认已通过身份验证的用户发出请求后,就可以在数据库中创建笔记。为此,我们现在将为作者分配传递给解析器的用户ID。这将使我们能够从笔记本身中引用创建用户。

src/resolvers/mutation.js,添加以下内容:

// add the users context
newNote: async (parent, args, { models, user }) => {
  // if there is no user on the context, throw an authentication error
  if (!user) {
    throw new AuthenticationError('You must be signed in to create a note');
  }

  return await models.Note.create({
    content: args.content,
    // reference the author's mongo id
    author: mongoose.Types.ObjectId(user.id)
  });
}, 

最后一步是将交叉引用应用于我们数据库中的数据。为此,我们将需要更新MongoDB notes结构的author字段。在/src/models/note.js,如下更新作者字段:

author: {
    
     type: mongoose.Schema.Types.ObjectId,
  ref: 'User',
  required: true
} 

有了此结构,所有新笔记都将准确记录并从请求的上下文中引用的作者。让我们在GraphQL Playground中编写一个newNote请求来尝试一下:

mutation {
    
    
  newNote(content: "Hello! This is a user-created note") {
    
     id content
  }
} 

编写请求时,我们还必须确保在Authorization标头中传递JWT(请参见图8-1):

{
    
     "Authorization": "<YOUR_JWT>" } 

如何检索JWT

如果你没有JWT,可以执行signIn修改来检索。

8-1GraphQL Playground中的newNote请求。

目前,我们的API不会返回作者信息,但是我们可以通过在MongoDB shell中查找笔记来验证是否正确添加了作者。在终端窗口中,键入以下内容:

mongodb.notes.find({
    
    _id: ObjectId("A DOCUMENT ID HERE")}) 

返回的值应包括作者密钥,并带有对象ID的值。

用户更新和删除权限

现在,我们还可以将用户检查添加到我们的deleteNoteupdateNote请求中。这些将要求我们既检查是否将用户传递到上下文,又检查该用户是否是笔记的所有者。为此,我们将检查存储在数据库的author字段中的用户ID是否与传递到解析器上下文中的用户ID相匹配。

src/resolvers/mutation.js,如下更新deleteNote请求:

deleteNote: async (parent, { id }, { models, user }) => {
  // if not a user, throw an Authentication Error
  if (!user) {
    throw new AuthenticationError('You must be signed in to delete a note');
  }

  // find the note
  const note = await models.Note.findById(id);
  // if the note owner and current user don't match, throw a forbidden error
  if (note && String(note.author) !== user.id) {
    throw new ForbiddenError("You don't have permissions to delete the note");
  }

  try {
    // if everything checks out, remove the note
    await note.remove();
    return true;
  } catch (err) {
    // if there's an error along the way, return false
    return false;
  }
}, 

现在,也在src/resolvers/mutation.js,如下更新updateNote请求:

updateNote: async (parent, { content, id }, { models, user }) => { // if not a user, throw an Authentication Error
  if (!user) { throw new AuthenticationError('You must be signed in to update a note');
  } // find the note
  const note = await models.Note.findById(id); // if the note owner and current user don't match, throw a forbidden error
  if (note && String(note.author) !== user.id) { throw new ForbiddenError("You don't have permissions to update the note");
  } // Update the note in the db and return the updated note
  return await models.Note.findOneAndUpdate(
    {
      _id: id
    },
    {
      $set: {
        content
      }
    },
    { new: true }
  );
},

用户查询

通过更新现有的请求包括用户检查,我们还添加一些特定于用户的查询。
为此,我们将添加三个新查询:

  • user

    给定特定的用户名,返回用户的信息

  • users

    返回所有用户的列表

  • me

    返回当前用户的用户信息

在编写查询解析器代码之前,请将这些查询添加到GraphQL src/schema.js文件,如下所示:

type Query {
    
    
  ...
  user(username: String!): User
  users: [User!]!
  me: User!
} 

现在在src/resolvers/query.js文件,编写以下解析程序查询代码:

module.exports = {
    
    
  // ...
  // add the following to the existing module.exports object:
  user: async (parent, {
    
     username }, {
    
     models }) => {
    
    
    // find a user given their username
    return await models.User.findOne({
    
     username });
  },
  users: async (parent, args, {
    
     models }) => {
    
    
    // find all users
    return await models.User.find({
    
    });
  },
  me: async (parent, args, {
    
     models, user }) => {
    
    
    // find a user given the current user context
    return await models.User.findById(user.id);
  }
} 

让我们看看这些在我们的GraphQL Playground中的结构。首先,我们可以编写一个用户查询来查找特定用户的信息。确保使用已经创建的用户名:

query {
    
    
  user(username:"adam") {
    
    
    username
    email id }
} 

这将返回一个数据对象,其中包含指定用户的用户名、电子邮件和ID值(图8-2)。

8-2GraphQL Playground中的用户查询

现在要查询数据库中的所有用户,我们可以使用users查询,这将返回一个包含所有用户信息的数据对象(图8-3):

query { users { username email
    id
  }
} 

8-3。用户在GraphQL Playground中查询

现在,我们可以使用传递给HTTP标头的JWT,通过me查询来查找有关登录用户的信息。

首先,请确保在GraphQL PlaygroundHTTP标头部分中包含令牌:

{
    
     "Authorization": "<YOUR_JWT>" } 

现在,像这样执行me查询(图8-4):

query { me { username email
    id
  }
} 

8-4GraphQL Playground中的me查询

有了这些解析器后,我们现在可以查询API以获得用户信息。

切换笔记收藏夹

我们还有最后一项功能可以添加到我们的用户交互中。你可能还记得我们的应用程序规范指出:“用户将能够收藏其他用户的笔记,并检索他们的收藏夹列表。类似于Twitter的“心”和Facebook的“喜欢”,我们希望用户能够将笔记标记(或取消标记)为收藏。为了实现此行为,我们将修改遵循GraphQL模式的标准模式,然后是数据库模块,最后是resolver函数。

首先,我们将在/src/schema.js中更新GraphQL模式。通过向我们的Note类型添加两个新属性来实现。 favoriteCount将跟踪笔记收到的“收藏夹”总数。 favoritedBy将包含一组喜欢笔记的用户。

type Note {
    
    
  // add the following properties to the Note type
  favoriteCount: Int!
  favoritedBy: [User!]
} 

我们还将添加收藏夹列表到我们的用户类型:

type User {
    
    
   // add the favorites property to the User type
   favorites: [Note!]!
 } 

接下来,我们将/src/schema.js在中添加一个请求,称为toggleFavorite,它将添加或删除指定笔记的收藏夹。此请求以笔记ID作为参数,并返回指定的笔记。

type Mutation {
    
    
  // add toggleFavorite to the Mutation type
  toggleFavorite(id: ID!): Note!
} 

接下来,我们需要更新笔记模块,在数据库中包括favoriteCountfavoritedBy属性。 最喜欢的数字将是一个数字类型,默认值为0。 最喜欢的数字将是一个对象数组,其中包含对我们数据库中用户对象ID的引用。我们完整的/src/models/note.js文件如下所示:

const noteSchema = new mongoose.Schema(
 {
    
    
   content: {
    
    
     type: String,
     required: true
   },
   author: {
    
    
     type: String,
     required: true
   },
   // add the favoriteCount property
   favoriteCount: {
    
    
     type: Number,
     default: 0
   },
   // add the favoritedBy property
   favoritedBy: [
     {
    
    
       type: mongoose.Schema.Types.ObjectId,
       ref: 'User'
     }
   ]
 },
 {
    
    
   // Assigns createdAt and updatedAt fields with a Date type
   timestamps: true
 }
); 

随着我们的GraphQL模式和数据库模块的更新,我们可以编写toggleFavorite请求。此请求将收到一个笔记ID作为参数,并检查用户是否已被列在“ favouritedBy”数组中。如果列出了该用户,我们将通过减少favoriteCount并从列表中删除该用户来删除收藏夹。如果用户尚未收藏该笔记,则我们将favouriteCount增加1,然后将当前用户添加到favouritedBy数组中。为此,请将以下代码添加到src/resolvers/mutation.js文件:

toggleFavorite: async (parent, {
    
     id }, {
    
     models, user }) => {
    
    
  // if no user context is passed, throw auth error
  if (!user) {
    
    
    throw new AuthenticationError();
  }

  // check to see if the user has already favorited the note
  let noteCheck = await models.Note.findById(id);
  const hasUser = noteCheck.favoritedBy.indexOf(user.id);

  // if the user exists in the list
  // pull them from the list and reduce the favoriteCount by 1
  if (hasUser >= 0) {
    
    
    return await models.Note.findByIdAndUpdate(
      id,
      {
    
    
        $pull: {
    
    
          favoritedBy: mongoose.Types.ObjectId(user.id)
        },
        $inc: {
    
    
          favoriteCount: -1
        }
      },
      {
    
    
        // Set new to true to return the updated doc
        new: true
      }
    );
  } else {
    
    
    // if the user doesn't exist in the list
    // add them to the list and increment the favoriteCount by 1
    return await models.Note.findByIdAndUpdate(
      id,
      {
    
    
        $push: {
    
    
          favoritedBy: mongoose.Types.ObjectId(user.id)
        },
        $inc: {
    
    
          favoriteCount: 1
        }
      },
      {
    
    
        new: true
      }
    );
  }
}, 

使用此代码后,让我们测试一下在GraphQL Playground中切换喜欢的笔记的功能。让我们用一个新创建的笔记来做到这一点。我们将首先编写一个newNote请求,确保包含一个带有有效JWTAuthorization标头(图8-5):

mutation {
    
    
  newNote(content: "Check check it out!") {
    
    
    content
    favoriteCount id }
} 

8-5。一个newNote请求

你会注意到该新笔记的收藏夹计数自动设置为0,因为这是我们在数据模块中设置的默认值。现在,让我们编写一个toggleFavorite请求’'以将其标记为收藏,将笔记的ID作为参数传递。同样,请确保包括带有有效JWTAuthorization HTTP标头。

mutation {
    
    
  toggleFavorite(id: "<YOUR_NOTE_ID_HERE>") {
    
    
    favoriteCount
  }
} 

运行此修改后,笔记的favoriteCount的值应为1。如果重新运行该请求,则favoriteCount将减少为0(图8-6)。

8-6toggleFavorite修改

用户现在可以在收藏夹中标记和取消标记笔记。更重要的是,我希望该功能可以演示如何向GraphQL应用程序的API添加新功能。

嵌套查询

GraphQL的一大优点是我们可以嵌套查询,使我们可以编写单个查询来精确返回所需的数据,而不是用多个查询。

我们的用户类型的GraphQL模式包括以数组格式列出作者的笔记列表,而我们的笔记类型包括对其作者的引用。所以,我们可用于从用户查询中提取笔记列表,或从笔记查询中获取作者信息。

这意味着我们可以编写如下查询:

query {
    
    
  note(id: "5c99fb88ed0ca93a517b1d8e") {
    
    
    id
    content
    # the information about the author note
    author {
    
    
      username
      id
    }
  }
} 

如果现在我们尝试运行类似于上一个查询的嵌套查询,则会收到错误消息。这是因为我们尚未编写用于对此信息执行数据库查找的解析程序代码。要启用此功能,我们将在src/resolvers目录中添加两个新文件。

src/resolvers/note.js,添加以下内容:

module.exports = {
  // Resolve the author info for a note when requested
  author: async (note, args, { models }) => {
    return await models.User.findById(note.author);
  },
  // Resolved the favoritedBy info for a note when requested
  favoritedBy: async (note, args, { models }) => {
    return await models.User.find({ _id: { $in: note.favoritedBy } });
  }
}; 

src/resolvers/user.js,添加以下内容:

module.exports = {
    
    
  // Resolve the list of notes for a user when requested
  notes: async (user, args, {
    
     models }) => {
    
    
    return await models.Note.find({
    
     author: user._id }).sort({
    
     _id: -1 });
  },
  // Resolve the list of favorites for a user when requested
  favorites: async (user, args, {
    
     models }) => {
    
    
    return await models.Note.find({
    
     favoritedBy: user._id }).sort({
    
     _id: -1 });
  }
}; 

现在我们需要更新src/resolvers/index.js导入和导出这些新的解析器模块。总体而言,src/resolvers/index.js文件现在应如下所示:

const Query = require('./query');
const Mutation = require('./mutation');
const Note = require('./note');
const User = require('./user');
const {
    
     GraphQLDateTime } = require('graphql-iso-date');

module.exports = {
    
    
  Query,
  Mutation,
  Note,
  User,
  DateTime: GraphQLDateTime
}; 

现在,如果我们编写一个嵌套的GraphQL查询或修改,我们将收到我们期望的信息。你可以通过编写以下笔记查询来进行尝试:

query {
    
    
  note(id: "<YOUR_NOTE_ID_HERE>") {
    
    
    id
    content
    # the information about the author note
    author {
    
    
      username
      id
    }
  }
} 

该查询应使用作者的用户名和ID正确解析。另一个实际的示例是返回有关“喜欢”笔记的用户的信息:

mutation {
    
    
  toggleFavorite(id: "<YOUR NOTE ID>") {
    
    
    favoriteCount
    favoritedBy {
    
    
      username
    }
  }
} 

使用嵌套的解析器,我们可以编写精确的查询和修改,以精确返回所需的数据。

结论

恭喜你!在本章中,我们的API逐渐成为一种用户可以真正与之交互的东西。该API通过集成用户操作/添加新功能和嵌套解析器来展示GraphQL的真正功能。我们还遵循了一种将真实的代码添加到项目中的尝试模式:首先编写GraphQL模式,然后编写数据库模块,最后编写解析器代码以查询或更新数据。通过将过程分为三个步骤,我们可以向我们的应用程序添加各种功能。在下一章中,我们将介绍使API产品准备就绪所需的最后步骤,包括分页和安全性。

如果有理解不到位的地方,欢迎大家纠错。如果觉得还可以,麻烦您点赞收藏或者分享一下,希望可以帮到更多人。

猜你喜欢

转载自blog.csdn.net/code_maomao/article/details/110161028