DEV Community

Cover image for MemoryLane: Building A Dynamic Time Capsule App with Pinata FILE API
DrPrime01
DrPrime01

Posted on

MemoryLane: Building A Dynamic Time Capsule App with Pinata FILE API

This is a submission for the The Pinata Challenge

What I Built

I built MemoryLane, a digital time capsule application that allows users to preserve moments and thoughts as files and rediscover them in the future. The project combines the nostalgia of traditional time capsules with modern cloud storage technology, leveraging Pinata's File API for secure and efficient file management.

Key features of MemoryLane include:

  • Digital preservation of memories
  • Future revelations based on user-set dates
  • Flexible time-frames for capsule opening
  • Multi-media experiences (supporting various file types)
  • Personal and collaborative options
  • Security and privacy
  • Cloud-based reliability

The application was built using:

  • NextJS (TypeScript variant)
  • Pinata SDK for file uploads and management
  • React-dropzone for handling file inputs
  • Axios for API calls
  • Date-fns for date formatting

Technical implementation highlights:

  1. Project setup with NextJS and necessary dependencies
  2. Integration with Pinata using environment variables for API keys
  3. Custom components for file upload and time capsule display
  4. Server-side API route for handling file uploads to Pinata
  5. Client-side state management for time capsules using React hooks
  6. Local storage integration for persisting time capsules between sessions
  7. Deployment to Vercel for hosting the application

The project demonstrates the use of Pinata's features including:

  • File uploads using the Pinata SDK
  • Creation of signed URLs for secure file access
  • Integration with NextJS API routes for server-side file handling

This project showcases how Pinata can be effectively used in a Web2 application, highlighting its versatility beyond Web3 and dApp development.

Demo

Link: MemoryLane

Image description

Image description

My Code

MemoryLane GitHub

More Details

In the MemoryLane project, Pinata's features were integral to the core functionality of storing and retrieving time capsule files. Here are detailed examples of how Pinata was used:

  1. Pinata SDK Integration: The project uses Pinata SDK for seamless interaction with Pinata's services. This is set up in the utils/config.ts file:
   import { PinataSDK } from "pinata"

   export const pinata = new PinataSDK({
     pinataJwt: `${process.env.PINATA_JWT}`,
     pinataGateway: `${process.env.NEXT_PUBLIC_PINATA_GATEWAY_URL}`
   })
Enter fullscreen mode Exit fullscreen mode

This configuration allows the application to use Pinata's services throughout the server-side code.

  1. File Upload to Pinata: In the api/files/route.ts file, we handle file uploads using Pinata's SDK:
   import { NextResponse, NextRequest } from "next/server";
import { pinata } from "@/utils/config";

export async function POST(req: NextRequest) {
  try {
    const data = await req.formData();
    const file: File | null = data.get("file") as unknown as File;
    const uploadData = await pinata.upload.file(file);
    const url = await pinata.gateways.createSignedURL({
      cid: uploadData.cid,
      expires: 360000,
    });
    return NextResponse.json({ url }, { status: 200 });
  } catch (e) {
    console.error(e);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

This code snippet demonstrates how we use Pinata to upload files directly from the user's input.

  1. Creating Signed URLs: After uploading a file, we generate a signed URL for secure access:
   const url = await pinata.gateways.createSignedURL({
     cid: uploadData.cid,
     expires: 360000,
   });
Enter fullscreen mode Exit fullscreen mode

This signed URL ensures that only authorized users can access the uploaded files, enhancing the security of our time capsules.

  1. Frontend Integration: On the frontend, we use these signed URLs to display images securely:
   import Image from "next/image";
import { format } from "date-fns";

import InfoIcon from "@/assets/icons/info-icon";
import LockIcon from "@/assets/icons/lock-icon";
import { TimeCapsuleStateType } from "@/types";

export default function TimeCapsule({
  url,
  openDate,
  created_at,
}: TimeCapsuleStateType) {
  return (
    <div className="flex flex-col gap-y-2 max-w-[240px]">
      <div className="relative">
        <Image
          src={url}
          width={240}
          height={240}
          alt="time capsule"
          className="object-cover aspect-square"
        />
        {new Date() < new Date(openDate) && (
          <div className="absolute bg-black/85 inset-0 flex items-center justify-center">
            <LockIcon />
          </div>
        )}
      </div>
      <p className="text-sm font-medium text-gray-700">
        To be opened on {format(openDate, "dd/MM/yyyy")}
      </p>
      <div className="bg-gray-200 flex space-x-1.5 p-2 rounded-xl">
        <InfoIcon />
        <p className="text-xs text-gray-500">
          This time capsule was created on {format(created_at, "dd/MM/yyyy")}
        </p>
      </div>
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

The url here is the signed URL returned from Pinata, allowing us to display the time capsule images without exposing the raw file locations.

  1. Configuring for Pinata's Gateway: To ensure NextJS can properly handle images from Pinata's gateway, we configured the next.config.mjs file:
   const nextConfig = {
     images: {
       remotePatterns: [
         {
           protocol: "https",
           hostname: "your-gateway-url.mypinata.cloud",
         },
       ],
     },
   };
Enter fullscreen mode Exit fullscreen mode

This configuration allows NextJS to optimize and serve images from Pinata's gateway efficiently.

By leveraging these features of Pinata, MemoryLane achieves:

  • Secure file storage: Files are stored on Pinata's decentralized network, ensuring durability and availability.
  • Controlled access: Signed URLs provide a secure way to access files, perfect for the time-release nature of digital time capsules.
  • Efficient retrieval: Pinata's gateway ensures fast access to files when it's time to "open" a time capsule.

See here for the full article, explaining the implementation from start to finish.

Cover image by ChatGPT

Top comments (4)

Collapse
 
programordie profile image
programORdie

Cool, but why does the URL expires? If the user wants to view his files a few years later, he won't have access to them anymore.

Collapse
 
drprime01 profile image
DrPrime01

The URL expires because the code uses Pinata's signed URL feature, which creates a temporary, access-controlled link to the file. It is for security reasons as it limits unauthorized access over time, and allows for temporary sharing without permanent file exposure.

As an alternative, you could use IPFS. It provides a permanent file storage option but offers less control over access compared to the signed URL.

Here is how to create one from the signed URL and return both.

import { NextResponse, NextRequest } from "next/server";
import { pinata } from "@/utils/config";

export async function POST(req: NextRequest) {
  try {
    const data = await req.formData();
    const file: File | null = data.get("file") as unknown as File;
    const uploadData = await pinata.upload.file(file);

    const cid = uploadData.cid;
    const signedUrl = await pinata.gateways.createSignedURL({
      cid,
      expires: 360000,
    });

    // Create a permanent IPFS gateway URL
    const permanentUrl = `https://gateway.pinata.cloud/ipfs/${cid}`;

    return NextResponse.json({ 
      cid,
      signedUrl,
      permanentUrl
    }, { status: 200 });
  } catch (e) {
    console.error(e);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

You could go for the signedURL or IPFS option based on your application's requirements. It all depends on the app you're cooking.

Collapse
 
programordie profile image
programORdie

I know, I submitted my self, but I just thought it’s kinda weird if the user wants to save stuff. Also, if you use a temporary url, it would be better to delete the file after it expired, to clean up space.

Thread Thread
 
drprime01 profile image
DrPrime01

Well, it depends on the use case as both options offer different security features. And, yeah, deleting the file after the expiration date is a good idea.