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:
- Project setup with NextJS and necessary dependencies
- Integration with Pinata using environment variables for API keys
- Custom components for file upload and time capsule display
- Server-side API route for handling file uploads to Pinata
- Client-side state management for time capsules using React hooks
- Local storage integration for persisting time capsules between sessions
- 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
My Code
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:
- 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}`
})
This configuration allows the application to use Pinata's services throughout the server-side code.
- 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 }
);
}
}
This code snippet demonstrates how we use Pinata to upload files directly from the user's input.
- 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,
});
This signed URL ensures that only authorized users can access the uploaded files, enhancing the security of our time capsules.
- 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>
);
}
The url
here is the signed URL returned from Pinata, allowing us to display the time capsule images without exposing the raw file locations.
- 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",
},
],
},
};
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)
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.
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.
You could go for the signedURL or IPFS option based on your application's requirements. It all depends on the app you're cooking.
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.
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.