2022 年 4 月 28 日

使用 Remix、Prisma 和 MongoDB 构建全栈应用:引用完整性和图像上传

12 分钟阅读

欢迎来到本系列的第四篇文章,您将在其中学习如何从头开始使用 MongoDB、Prisma 和 Remix 构建全栈应用程序!在本文中,您将构建应用程序的个人资料设置部分,包括图像上传组件,并配置架构以在数据中提供引用完整性。

Build A Fullstack App with Remix, Prisma & MongoDB: Referential Integrity & Image Uploads

目录

介绍

在本系列的前一部分中,您构建了此应用程序的主要区域,包括赞誉 Feed、用户列表、最近的赞誉列表和赞誉发送表单。

在本文中,您将通过构建一种允许用户更新其个人资料信息和上传个人资料图片的方式来完成此应用程序的开发。您还将对您的架构进行一些更改,这将为您的数据库提供引用完整性。

注意:此项目的起点位于 GitHub 存储库的 part-3 分支中。如果您想查看本文的最终结果,请前往 part-4 分支。

开发环境

为了跟上提供的示例,您需要 ...

注意:可选扩展为 Tailwind 和 Prisma 添加了一些非常好的智能感知和语法高亮。

构建个人资料设置模态框

您应用程序的个人资料设置页面将显示在一个模态框中,该模态框通过单击页面右上角的个人资料设置按钮访问。

app/components/search-bar.tsx

  • 向导出的组件添加一个名为 profile 的新 prop,其类型为 Prisma 生成的 Profile 类型
  • 导入 UserCircle 组件。
  • form 内容的末尾渲染 UserCircle 组件,并将新的 profile prop 数据传递给它。这将充当您的个人资料设置按钮。

如果您的开发服务器已经在运行,这将导致您的主页抛出错误,因为 SearchBar 组件现在需要 profile 数据。

app/routes/home.tsx 文件中,使用本系列的第二部分app/utils/auth.server.ts 编写的 getUser 函数。使用此函数在 loader 函数内部加载已登录用户的数据。然后将该数据提供给 SearchBar 组件。

您的 SearchBar 现在将有权访问它需要的 profile 数据。如果您之前因缺少此数据而收到错误,则刷新浏览器中的页面应显示个人资料按钮在页面右上角成功呈现。

创建模态框

目标是在单击个人资料设置按钮时打开个人资料设置模态框。与本系列的上一节中构建的赞誉模态框类似,您需要设置一个嵌套路由,您将在其中渲染新的模态框。

app/routes/home 中添加一个名为 profile.tsx 的新文件,其中包含以下内容以开始

上面的代码片段 ...

  • ... 将模态框渲染到新的 ProfileSettings 组件中。
  • ... 在 loader 函数中检索并返回已登录用户的数据。
  • ... 使用 useLoaderData hook 来访问从 loader 函数返回的 user 数据。

要打开这个新的模态框,请在 app/components/search-bar.tsx 中向 UserCircle 组件添加一个 onClick 处理程序,该处理程序使用 Remix 的 useNavigate hook 将用户导航到 /home/profile 子路由。

如果您现在单击个人资料设置按钮,您应该会在屏幕上看到新的模态框。

构建表单

您将构建的表单将有三个字段,允许用户修改其个人资料详细信息:名字、姓氏和部门。

通过添加名字和姓氏输入开始构建表单

以下是上面添加内容的概述

  1. 添加了所做更改中需要的导入。
  2. 在 state 中创建了一个 formData 对象,用于保存表单的值。这会将这些值默认为已登录用户的现有个人资料数据。
  3. 创建了一个函数,该函数接收 HTML change 事件和一个字段名称作为参数。这些用于在组件中输入字段的值更改时更新 formData 状态。
  4. 渲染表单的基本布局以及两个输入字段。

此时,尚未进行错误处理,并且表单不执行任何操作。在添加这些部分之前,您需要添加部门下拉列表。

app/utils/constants.ts 中,添加一个新的 departments 常量,以保存 Prisma 架构中定义的可能选项。将以下导出添加到该文件

departments 以及 SelectBox 组件导入到您的 app/routes/home/profile.tsx 文件中,并使用它们向您的表单添加新输入

此时,您的表单应渲染正确的输入及其选项。它会将其值默认为与已登录用户个人资料关联的当前值。

允许用户提交表单

您将构建的下一部分是 action 函数,它将使此表单起作用。

在您的 app/routes/home/profile.tsx 中,添加一个 action 函数,该函数从 request 对象检索表单数据,并验证 firstNamelastNamedepartment 字段

上面的 action 函数执行以下操作

  1. request 对象中提取您需要的表单数据点。
  2. 确保您关心的每条数据都是 string 数据类型。
  3. 使用先前编写的 validateName 函数验证数据。
  4. 重定向到 /home 路由,关闭设置模态框。

上面的代码片段还在各种验证失败时抛出相关错误。为了使用经过验证的数据,编写一个允许您更新用户的函数。

app/utils/user.server.ts 中,导出以下函数

此函数将允许您传入任何 profile 数据并更新 id 与提供的 userId 匹配的用户。

回到 app/routes/home/profile.tsx 文件,导入该新函数并使用它在 action 函数中更新已登录用户

现在,当用户点击保存按钮时,他们更新后的个人资料数据将被存储,并且模态框将被关闭。

添加图像上传组件

设置 AWS 账户

您的用户现在能够更新其个人资料中的一些关键信息,但是,最好添加的一件事是允许用户设置个人资料图片,以便其他用户可以更轻松地识别他们。

为此,您将设置一个 AWS S3 文件存储桶来保存上传的图像。如果您还没有 AWS 账户,可以在此处注册。

注意:亚马逊提供免费套餐,让您可以免费访问 S3。

创建 IAM 用户

拥有账户后,您将需要在 AWS 中设置 身份访问管理 (IAM) 用户,以便您可以生成访问密钥 ID秘密密钥,这两者都是与 S3 交互所必需的。

注意:如果您已经拥有 IAM 用户及其密钥,请随时跳过。

前往 AWS 控制台主页。在页面右上角,单击标有您的用户名的下拉列表,然后选择安全凭证

进入该部分后,点击左侧菜单访问管理下的用户选项。

在此页面上,单击页面右上角的添加用户按钮。

这将引导您完成一个简短的向导,该向导允许您配置您的用户。按照以下步骤操作

第一部分要求

  1. 用户名:提供任何用户名。
  2. 选择 AWS 访问类型:选择访问密钥 - 程序化访问选项,该选项启用访问密钥 ID秘密密钥的生成。

在向导的第二步中,进行以下选择

  1. 选择“直接附加现有策略”选项。
  2. 搜索术语“S3”。
  3. 点击标记为 AmazonS3FullAccess 的选项旁边的复选框。
  4. 点击表单底部的“下一步”。

如果您想向您的用户添加标签以帮助更轻松地管理和组织您帐户中的用户,请在向导的第三步在此处添加这些标签。当您在此页面上完成操作后,点击下一步

如果此页面上的摘要看起来不错,请点击页面底部的创建用户按钮。

点击该按钮后,您将进入一个页面,其中包含您的访问密钥 ID秘密密钥。复制这些并将其存储在您可以轻松访问的地方,因为您很快就会使用它们。

设置 S3 存储桶

现在您已经有了用户和访问密钥,请前往 AWS S3 仪表板,您将在其中设置文件存储桶。

在此页面右上角,点击创建存储桶按钮。

系统将要求您提供存储桶的名称和区域。填写这些详细信息并将您选择的值与您先前保存的访问密钥 ID秘密密钥一起保存。您稍后也需要这些。

填写完这些内容后,点击表单底部的创建存储桶

存储桶创建完成后,您将被发送到存储桶仪表板页面上的对象选项卡。导航到权限选项卡。

在此选项卡中,点击阻止公共访问部分下的编辑按钮。在此表单中,取消选中阻止所有公共访问框,然后点击保存更改。这将您的存储桶设置为公共,这将允许您的应用程序访问图像。

在该部分下方,您将看到一个存储桶策略部分。粘贴以下策略,并确保将 <bucket-name> 替换为您的存储桶名称。此策略将允许您的图像被公开读取

您现在已经设置了 AWS 用户和 S3 存储桶。接下来,您需要将密钥和存储桶配置保存到您的 .env 文件中,以便以后可以使用它们。

更新您的 Prisma 架构

您现在将在数据库中创建一个字段,您将在其中存储上传图像的链接。这些应与 Profile 嵌入式文档一起存储,因此请向您的 Profile 类型块添加一个新字段。

要使用这些更改更新 Prisma Client,请运行 npx prisma generate

构建图像上传组件

app/components 中创建一个名为 image-uploader.tsx 的新文件,其中包含以下内容

上面的代码片段是完整的图像上传组件。以下是正在发生的事情的概述

  1. 定义了一个 preventDefault 函数来处理组件中文件输入的更改。
  2. 定义了一个 handleDrop 函数来处理组件中文件输入上的 drop 事件。
  3. 定义了一个 handleChange 函数来处理组件中文件输入上的任何 change 事件。
  4. 渲染了一个 div,其中定义了各种事件处理程序,使其能够对文件拖放、拖动事件和点击做出反应。这些用于触发图像上传和样式更改,这些更改仅在元素接收拖动事件时出现。

每当此组件中 input 的值更改时,都会调用 props 中的 onChange 函数,并传递文件数据。该数据将上传到 S3。

接下来创建将处理图像上传的服务。

构建图像上传服务

要构建您的图像上传服务,您将需要两个新的 npm 包

  • aws-sdk:公开了一个 JavaScript API,允许您与 AWS 服务进行交互。
  • cuid:一种用于生成唯一 ID 的工具。您将使用它来生成随机文件名。

您的图像上传服务将位于一个新的实用程序文件中。在 app/utils 中创建一个名为 s3.server.ts 的文件。

为了处理上传,您将使用 Remix 的 unstable_parseMultipartFormData 函数,该函数处理 request 对象的 multipart/form-data 值。

注意multipart/form-data 是在表单中发布整个文件时的表单数据类型。

unstable_parseMultipartFormData 将接收两个参数

  1. 从表单提交中检索到的 request 对象。
  2. uploadHandler 函数,它流式传输文件数据并处理上传。

注意unstable_parseMultipartFormData 函数的使用方式类似于我们过去使用过的 Remix 的 request.formData 函数。

将以下函数和导入添加到您创建的新文件中

此代码设置您的 S3 API,以便您可以与您的存储桶进行交互。它还添加了 uploadHandler 函数。此函数

  1. 使用您在设置 AWS 用户和 S3 存储桶时存储的环境变量来设置 S3 SDK。
  2. 只要数据键的名称为 'profile-pic',就从 request 流式传输文件数据。
  3. 将文件上传到 S3。
  4. 返回 S3 返回的 Location 数据,其中包括新文件在 S3 中的 URL 位置。

现在 uploadHandler 已完成,添加另一个实际接收 request 对象并将其与 uploadHandler 一起传递到 unstable_parseMultipartFormData 函数中的函数。

此函数被传递一个 request 对象,该对象稍后将从 action 函数发送过来。

文件数据通过 uploadHandler 函数传递,该函数处理上传到 S3 的操作,而 formData 会将新文件的位置返回给您,该位置位于表单数据对象内部。然后从该对象中提取 'profile-pic' URL 并由该函数返回。

将组件和服务投入使用

现在实现正常工作的个人资料图片上传所需的两个部分已完成,将它们放在一起。

通过在 app/routes 中创建一个名为 avatar.ts 的新文件,添加一个处理您的上传表单数据的资源路由,其中包含以下 action 函数

上面的函数执行以下步骤来处理上传表单

  1. 获取请求用户的 id
  2. 上传请求数据中传递的文件。
  3. 使用新的 profilePicture URL 更新请求用户的个人资料数据。
  4. 使用 imageUrl 变量响应 POST 请求。

现在您可以使用 ImageUploader 组件来处理文件上传并将文件数据发送到这个新的 /avatar 路由。

app/routes/home/profile.tsx 中,导入 ImageUploader 组件并将其添加到表单中输入字段的左侧。

还要添加一个新函数来处理 ImageUploader 组件发出的 onChange 事件,并在 formData 变量中添加一个新字段来存储个人资料图片数据。

现在,如果您转到该表单并尝试上传文件,则数据应在 S3、数据库和表单的状态中正确保存。

显示个人资料图片

太棒了!图像上传运行顺畅,现在您只需要在网站上用户头像出现的所有位置显示这些图像。

打开 app/components/user-circle.tsx 中的 UserCircle 组件,并进行这些更改以将头像的背景图像设置为个人资料图片(如果可用)

如果您现在为您的几个用户提供个人资料图片,您应该会在整个网站上看到这些图片!

添加删除帐户功能

您的个人资料设置模态框需要的最后一个功能是删除帐户的能力。

删除数据,尤其是在无模式数据库中,可能会创建“孤立文档”,即曾经与父文档相关的文档,但其父文档在某个时候被删除。

您将在本节中采取保护措施来防止这种情况。

添加删除按钮

您将以类似于处理注册和登录表单的方式处理此表单。此表单将发送一个 _action 键,以便 action 函数知道它接收到哪种类型的请求。

app/routes/home/profile.tsx 中,对 ProfileSettings 函数中返回的 form 进行以下更改

现在,根据单击的按钮,您可以在 action 函数中处理不同的 _action

更新 action 函数以使用 switch 语句来执行不同的操作

现在,如果用户保存表单,将命中 'save' 分支,并且现有功能将继续执行。然而,'delete' 分支目前什么也不做。

app/utils/user.server.ts 中添加一个新函数,该函数接受一个 id 并删除与之关联的用户

您现在可以填写个人资料页面上 "delete" 分支的其余部分。

您的用户现在可以删除他们的帐户了!

更新数据模型以添加引用完整性

此删除用户功能的唯一问题是,当用户被删除时,他们所有已创作的赞誉都会变成孤立数据

您可以使用引用操作来触发删除作者时删除任何赞誉。

运行 npx prisma db push 以传播这些更改并生成 Prisma 客户端。

现在,如果您删除一个帐户,该帐户创作的任何 Kudos 也将随之删除!

添加表单验证

您即将完成!最后一步是在个人资料设置表单中连接错误消息处理。

您的 action 函数已经返回了所有正确的错误消息;它们只需要被处理。

app/routes/home/profile.tsx 中进行以下更改以处理这些错误

以上代码片段中进行了以下更改

  1. useActionData 钩子被用于检索错误消息。这些消息存储在状态变量中,并在用户在提交错误表单后返回到模态框的情况下用于填充表单。
  2. 添加了错误输出以显示任何表单级别的错误。
  3. 错误数据被传递给 FormField 组件,以允许它们在需要时显示其字段级别的错误。

在进行上述更改后,您将看到任何表单和验证错误都显示在表单上。

总结 & 接下来是什么

通过本文中所做的更改,您已成功完成 Kudos 应用程序!网站的所有部分都已正常运行,可以交付给您的用户了。

在本节中,您学习了

  • Remix 中的嵌套路由
  • AWS S3
  • Prisma 和 MongoDB 的引用操作和完整性

在本系列的下一节中,您将完成收尾工作,并将您构建的应用程序部署到 Vercel!

不要错过下一篇文章!

注册 Prisma 新闻通讯