使用 Next.js、Vercel Blob 和 Postgres 构建开源 DocSend 替代方案

本文介绍了如何使用Next.js、VercelBlob和Postgres构建一个名为Papermark的开源工具,作为DocSend的安全文档共享平台替代品。文章详细阐述了设置项目环境、文件上传、生成文档链接以及查看文档的步骤,旨在提供一个动态且可定制化的解决方案。

您将在本文中找到什么?

您可能遇到过用于安全文档共享、跟踪和存储的平台,例如 DocSend、Dropbox、Google Drive,并且这个列表还在继续。底层功能是您上传文档,与个人或群组共享链接,他们可以通过唯一链接查看文档。

动图

Papermark - DocSend 的第一个动态开源替代品。

简单介绍一下我们的背景。Papermark 是 DocSend 的动态开源替代品。我们主要帮助管理安全文档共享,包括实时分析。所有这些都是开源的。

如果你能给我们一颗星,我会非常高兴!并在评论中让我知道❤️

设置项目

在这里,我将指导您为文档共享应用程序创建项目环境。我们将设置 Next.js 应用程序。

摆茶具

在你开始之前,我建议你设置一个包管理器,就像tea处理你的开发环境一样。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>sh <<span style="color:var(--syntax-error-color)">(</span>curl https://tea.xyz<span style="color:var(--syntax-error-color)">)</span>

<span style="color:var(--syntax-comment-color)"># --- OR ---</span>
<span style="color:var(--syntax-comment-color)"># using brew</span>
brew <span style="color:var(--syntax-text-color)">install </span>tea
</code></span></span>

tea让您专注于开发,它将负责安装nodenpm以及vercel您可能需要进行开发的任何其他包。
只需键入命令,包就会可用 - 即使您之前没有安装它。
最好的部分是,tea将所有软件包安装在可重定位目录(默认:)~/.tea中,因此您不必担心污染系统文件。

使用 TypeScript 和 Tailwindcss 设置 Next.js

我们正在使用create-next-app生成一个新的 Next.js 项目。我们还将使用 TypeScript 和 Tailwind CSS,因此我们将在出现提示时选择这些选项。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>npx create-next-app

<span style="color:var(--syntax-comment-color)"># ---</span>
<span style="color:var(--syntax-comment-color)"># you'll be asked the following prompts</span>
What is your project named?  my-app
Would you like to add TypeScript with this project?  Y/N
<span style="color:var(--syntax-comment-color)"># select `Y` for typescript</span>
Would you like to use ESLint with this project?  Y/N
<span style="color:var(--syntax-comment-color)"># select `Y` for ESLint</span>
Would you like to use Tailwind CSS with this project? Y/N
<span style="color:var(--syntax-comment-color)"># select `Y` for Tailwind CSS</span>
Would you like to use the <span style="color:var(--syntax-string-color)">`</span>src/ directory<span style="color:var(--syntax-string-color)">`</span> with this project? Y/N
<span style="color:var(--syntax-comment-color)"># select `N` for `src/` directory</span>
What import <span style="color:var(--syntax-text-color)">alias </span>would you like configured? <span style="color:var(--syntax-string-color)">`</span>@/<span style="color:var(--syntax-declaration-color)">*</span><span style="color:var(--syntax-string-color)">`</span>
<span style="color:var(--syntax-comment-color)"># enter `@/*` for import alias</span>
</code></span></span>

设置 Vercel Blob

  1. 注册 vercel.com
  2. 转到Storage – Dashboard – Vercel
  3. 创建一个新的数据库;选择“斑点”
  4. 不要忘记将 @vercel/blob 添加到你的 package.json
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>   npm <span style="color:var(--syntax-text-color)">install</span> @vercel/blob
</code></span></span>
  1. 完成🎉

设置 Vercel Postgres

  1. 注册 vercel.com
  2. 转到Storage – Dashboard – Vercel
  3. 创建一个新的数据库;选择“Postgres”
  4. 完成🎉

设置棱镜

Prisma 是我们选择的数据库 ORM 层。它是与 Postgres 和其他数据库交互的绝佳工具。我们将使用它为我们的数据库创建模式并为我们的数据库模型生成 TypeScript 类型。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>npm <span style="color:var(--syntax-text-color)">install</span> @prisma/client
npm <span style="color:var(--syntax-text-color)">install </span>prisma <span style="color:var(--syntax-error-color)">--save-dev</span>
</code></span></span>

建筑应用

  1. 上传文件
  2. 为文档生成链接
  3. 查看文件

#1 将文档上传到 Vercel Blob

第一站是文件上传。在这里,我们将为用户将他们的文件上传到我们的平台奠定基础。这个过程包括创建一个上传文件的接口,利用 Next.js 和 Vercel Blob 来存储这些文档,以及实现处理这些文件的功能。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)">// pages/upload.tsx</span>
<span style="color:var(--syntax-comment-color)">// this is a simplified example of a file upload form in Next.js</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">useState</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">react</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">;</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">default</span> <span style="color:var(--syntax-declaration-color)">function</span> <span style="color:var(--syntax-name-color)">Form</span><span style="color:var(--syntax-text-color)">()</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-name-color)">currentFile</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">setCurrentFile</span><span style="color:var(--syntax-text-color)">]</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">useState</span><span style="color:var(--syntax-error-color)"><</span><span style="color:var(--syntax-name-color)">File</span> <span style="color:var(--syntax-error-color)">|</span> <span style="color:var(--syntax-declaration-color)">null</span><span style="color:var(--syntax-error-color)">></span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-declaration-color)">null</span><span style="color:var(--syntax-text-color)">);</span>

  <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">handleSubmit</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">event</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">any</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-name-color)">event</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">preventDefault</span><span style="color:var(--syntax-text-color)">();</span>

    <span style="color:var(--syntax-comment-color)">// code to send the file to a serverless API function `/api/upload` </span>
    <span style="color:var(--syntax-comment-color)">// to upload to Vercel Blob </span>
  <span style="color:var(--syntax-text-color)">};</span>

  <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-text-color)">(</span>
    <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">form</span> <span style="color:var(--syntax-name-color)">onSubmit</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">{</span><span style="color:var(--syntax-name-color)">handleSubmit</span><span style="color:var(--syntax-string-color)">}</span><span style="color:var(--syntax-text-color)">></span>
      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">h2</span><span style="color:var(--syntax-text-color)">></span>Upload a document<span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">h2</span><span style="color:var(--syntax-text-color)">></span>

      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">label</span><span style="color:var(--syntax-text-color)">></span>Upload a document<span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">label</span><span style="color:var(--syntax-text-color)">></span>
      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">input</span> <span style="color:var(--syntax-name-color)">type</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"file"</span> <span style="color:var(--syntax-name-color)">onChange</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">{</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">e</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-name-color)">setCurrentFile</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">e</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">target</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">files</span><span style="color:var(--syntax-text-color)">?.[</span><span style="color:var(--syntax-literal-color)">0</span><span style="color:var(--syntax-text-color)">]</span> <span style="color:var(--syntax-error-color)">||</span> <span style="color:var(--syntax-declaration-color)">null</span><span style="color:var(--syntax-text-color)">)</span><span style="color:var(--syntax-string-color)">}</span> <span style="color:var(--syntax-text-color)">/></span>

      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">label</span><span style="color:var(--syntax-text-color)">></span>Name<span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">label</span><span style="color:var(--syntax-text-color)">></span>
      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">input</span> <span style="color:var(--syntax-name-color)">type</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"text"</span> <span style="color:var(--syntax-name-color)">placeholder</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"Acme Presentation"</span> <span style="color:var(--syntax-text-color)">/></span>

      <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">button</span> <span style="color:var(--syntax-name-color)">type</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"submit"</span><span style="color:var(--syntax-text-color)">></span>Upload document<span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">button</span><span style="color:var(--syntax-text-color)">></span>
    <span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">form</span><span style="color:var(--syntax-text-color)">></span>
  <span style="color:var(--syntax-text-color)">);</span>
<span style="color:var(--syntax-text-color)">}</span>

</code></span></span>
<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)">// pages/api/upload.ts</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-error-color)">*</span> <span style="color:var(--syntax-declaration-color)">as</span> <span style="color:var(--syntax-name-color)">vercelBlob</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">@vercel/blob</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">;</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">NextResponse</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">NextRequest</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">next/server</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">;</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">config</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-name-color)">runtime</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">edge</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">,</span>
<span style="color:var(--syntax-text-color)">};</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">default</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-declaration-color)">function</span> <span style="color:var(--syntax-name-color)">upload</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">request</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">NextRequest</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">form</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-name-color)">request</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">formData</span><span style="color:var(--syntax-text-color)">();</span>
  <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">file</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">form</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-declaration-color)">get</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">file</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-declaration-color)">as</span> <span style="color:var(--syntax-name-color)">File</span><span style="color:var(--syntax-text-color)">;</span>

  <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">blob</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-name-color)">vercelBlob</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">put</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">file</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">name</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">file</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">access</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">public</span><span style="color:var(--syntax-string-color)">"</span> <span style="color:var(--syntax-text-color)">});</span>

  <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-name-color)">NextResponse</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">json</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">blob</span><span style="color:var(--syntax-text-color)">);</span>
<span style="color:var(--syntax-text-color)">}</span>
</code></span></span>

#2 生成文档链接

解决了上传问题后,让我们将目光投向下一个里程碑:为上传的文档生成一个唯一链接。这就是我们允许用户与他人共享文档的方式。从本质上讲,我们需要创建一个系统,其中每个文档都对应于我们应用程序上的唯一 URL。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)">// pages/api/create-link.ts</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">NextApiRequest</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">NextApiResponse</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">next</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">;</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-name-color)">prisma</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">@/lib/prisma</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">;</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">default</span> <span style="color:var(--syntax-declaration-color)">async</span> <span style="color:var(--syntax-declaration-color)">function</span> <span style="color:var(--syntax-name-color)">handle</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">req</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">NextApiRequest</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">res</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-name-color)">NextApiResponse</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-text-color)">{</span>
  <span style="color:var(--syntax-declaration-color)">if</span> <span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-name-color)">req</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">method</span> <span style="color:var(--syntax-error-color)">===</span> <span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-string-color)">POST</span><span style="color:var(--syntax-string-color)">"</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">name</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">url</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">req</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">body</span><span style="color:var(--syntax-text-color)">;</span>

    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-text-color)">document</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-declaration-color)">await</span> <span style="color:var(--syntax-name-color)">prisma</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-text-color)">document</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">create</span><span style="color:var(--syntax-text-color)">({</span>
      <span style="color:var(--syntax-name-color)">data</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-name-color)">name</span><span style="color:var(--syntax-text-color)">,</span>
        <span style="color:var(--syntax-name-color)">url</span><span style="color:var(--syntax-text-color)">,</span>
        <span style="color:var(--syntax-name-color)">link</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">{</span>
          <span style="color:var(--syntax-name-color)">create</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">{},</span>
        <span style="color:var(--syntax-text-color)">},</span>
      <span style="color:var(--syntax-text-color)">},</span>
      <span style="color:var(--syntax-name-color)">include</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-name-color)">link</span><span style="color:var(--syntax-text-color)">:</span> <span style="color:var(--syntax-declaration-color)">true</span><span style="color:var(--syntax-text-color)">,</span>
      <span style="color:var(--syntax-text-color)">}</span>
    <span style="color:var(--syntax-text-color)">});</span>

    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-name-color)">res</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">status</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-literal-color)">201</span><span style="color:var(--syntax-text-color)">).</span><span style="color:var(--syntax-name-color)">json</span><span style="color:var(--syntax-text-color)">({</span> <span style="color:var(--syntax-text-color)">document</span> <span style="color:var(--syntax-text-color)">});</span>
  <span style="color:var(--syntax-text-color)">}</span>
<span style="color:var(--syntax-text-color)">}</span>

</code></span></span>

当文档上传到 Vercel Blob 时,我们会将 blob URL(对该文档的引用)连同该文档的唯一标识符(如 UUID)一起存储在我们的 Postgres 数据库中。当有人导航到我们平台上包含此标识符的 URL 时,我们会从 Postgres 获取相应的 blob URL 并使用它来加载文档。

#3 查看文档

上传文档并生成唯一链接后,我们进入了任务的最后阶段:查看文档。我们需要为用户创建一种方式来查看他们通过唯一链接访问的文档。

我们可以通过创建一个与我们的文档链接的 URL 模式相匹配的新 Next.js 页面来做到这一点。此页面将使用 URL 中的文档标识符从我们的 Postgres 数据库中获取 blob URL,并从 Vercel Blob 加载文档。

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)">// pages/view/[linkId].tsx</span>
<span style="color:var(--syntax-comment-color)">// this is a simple example of a Next.js page that loads a document</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-name-color)">React</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-name-color)">useEffect</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">useState</span><span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">'</span><span style="color:var(--syntax-string-color)">react</span><span style="color:var(--syntax-string-color)">'</span>
<span style="color:var(--syntax-declaration-color)">import</span> <span style="color:var(--syntax-text-color)">{</span><span style="color:var(--syntax-name-color)">useRouter</span><span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-declaration-color)">from</span> <span style="color:var(--syntax-string-color)">'</span><span style="color:var(--syntax-string-color)">next/router</span><span style="color:var(--syntax-string-color)">'</span>

<span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">ViewDocumentPage</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-text-color)">()</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-text-color)">{</span>
    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-name-color)">router</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">useRouter</span><span style="color:var(--syntax-text-color)">()</span>
    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-text-color)">{</span> <span style="color:var(--syntax-name-color)">linkId</span> <span style="color:var(--syntax-text-color)">}</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">router</span><span style="color:var(--syntax-text-color)">.</span><span style="color:var(--syntax-name-color)">query</span>
    <span style="color:var(--syntax-declaration-color)">const</span> <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-name-color)">documentURL</span><span style="color:var(--syntax-text-color)">,</span> <span style="color:var(--syntax-name-color)">setDocumentURL</span><span style="color:var(--syntax-text-color)">]</span> <span style="color:var(--syntax-error-color)">=</span> <span style="color:var(--syntax-name-color)">useState</span><span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-declaration-color)">null</span><span style="color:var(--syntax-text-color)">)</span>

    <span style="color:var(--syntax-name-color)">useEffect</span><span style="color:var(--syntax-text-color)">(()</span> <span style="color:var(--syntax-error-color)">=></span> <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-comment-color)">// code to fetch the document URL from Postgres using linkUUID</span>
        <span style="color:var(--syntax-comment-color)">// and then set it as documentURL</span>
    <span style="color:var(--syntax-text-color)">},</span> <span style="color:var(--syntax-text-color)">[</span><span style="color:var(--syntax-name-color)">linkUUID</span><span style="color:var(--syntax-text-color)">])</span>

    <span style="color:var(--syntax-declaration-color)">if</span> <span style="color:var(--syntax-text-color)">(</span><span style="color:var(--syntax-error-color)">!</span><span style="color:var(--syntax-name-color)">documentURL</span><span style="color:var(--syntax-text-color)">)</span> <span style="color:var(--syntax-text-color)">{</span>
        <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">div</span><span style="color:var(--syntax-text-color)">></span>Loading...<span style="color:var(--syntax-text-color)"></</span><span style="color:var(--syntax-error-color)">div</span><span style="color:var(--syntax-text-color)">></span>
    <span style="color:var(--syntax-text-color)">}</span>

    <span style="color:var(--syntax-declaration-color)">return</span> <span style="color:var(--syntax-text-color)"><</span><span style="color:var(--syntax-error-color)">iframe</span> <span style="color:var(--syntax-name-color)">src</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">{</span><span style="color:var(--syntax-name-color)">documentURL</span><span style="color:var(--syntax-string-color)">}</span> <span style="color:var(--syntax-name-color)">width</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"100%"</span> <span style="color:var(--syntax-name-color)">height</span><span style="color:var(--syntax-text-color)">=</span><span style="color:var(--syntax-string-color)">"100%"</span> <span style="color:var(--syntax-text-color)">/></span>
<span style="color:var(--syntax-text-color)">}</span>

<span style="color:var(--syntax-declaration-color)">export</span> <span style="color:var(--syntax-declaration-color)">default</span> <span style="color:var(--syntax-name-color)">ViewDocumentPage</span>

</code></span></span>

结论

就这样,我们的编码冒险结束了。我们共同构建了 DocSend 的简单开源替代方案,利用了 Next.js 和 Vercel Blob 的强大功能。它允许用户上传、共享和查看文档,类似于许多商业平台,但具有我们的自定义功能。

感谢您阅读。我是 Marc,一名开源爱好者。我正在构建papermark.io - DocSend 的动态开源替代品。

对我来说,编码就是不断探索和学习。
快乐编码,朋友们!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值