This is a submission for the The Pinata Challenge
Pixxsha - Share Your Moments Your Way
Pixxsha is a photo-sharing platform that lets you securely store and share your precious memories. Born from the DEV.to and Pinata challenge, Pixxsha aims to improve how we preserve and share our moments.
What Makes Pixxsha Special?
Secure Storage: Your photos are stored on IPFS, ensuring they're safe and always accessible.
Smart and Flexible Sharing: Share photos publicly, privately, or with a time limit - you're in control.
/public
: Share your photos with anyone using a simple link.
/restricted
: Control who gets access by adding specific people.
/view Once
: Send a link that disappears after it’s been viewed.Organized Memories: Create custom groups for your photos, like "My 21st Birthday" or "Baby's First Year".
The Future of Pixxsha
We're just getting started! Here are some exciting features we're working on:
🌟 Life Timeline: Create a visual journey of your life with photo groups for significant milestones.
👨👩👧👦 Family Albums: Dedicated spaces for family memories, like "Baby's First Steps" or "Family Vacations".
🤝 Collaborative Groups: Share and contribute to group albums with friends and family.
Thank You, DEV Community and Pinata!
This challenge sparked the idea for Pixxsha, and we're excited to see where it goes. We're grateful for the opportunity to build something that could change how people cherish their memories.
Demo
Pixxsha's dashboard
Our Code
The GitHub repository for Pixxsha
More Details of how we integrated Pinata
Pinata and IPFS
We've integrated Pinata's Web3 SDK to handle our IPFS interactions. Here's a glimpse of how we're using it:
- From the
pinataService.ts
import { PinataSDK } from 'pinata-web3';
const pinata = new PinataSDK({
pinataJwt: process.env.PINATA_JWT!,
pinataGateway: process.env.PINATA_GATEWAY!,
});
// Upload a file to Pinata with optional tags and group assignment
export const uploadFile = async (file: File, tags: Record<string, string>, groupId?: string) => {
try {
const upload = await pinata.upload.file(file, {
metadata: {
name: file.name,
keyvalues: tags,
},
});
if (groupId) {
await pinata.groups.addCids({
groupId,
cids: [upload.IpfsHash],
});
}
return upload;
} catch (error) {
throw new Error(`Failed to upload file: ${error.message}`);
}
};
// Retrieve a file from Pinata using its IPFS hash
export const retrieveFile = async (ipfsHash: string) => {
try {
const data = await pinata.gateways.get(ipfsHash);
return data;
} catch (error) {
throw new Error(`Failed to retrieve file: ${error.message}`);
}
};
// Create a new group in Pinata
export const createGroup = async (name: string) => {
try {
return await pinata.groups.create({ name });
} catch (error) {
throw new Error(`Failed to create group: ${error.message}`);
}
};
// Get details of a specific group by ID
export const getGroup = async (groupId: string) => {
try {
return await pinata.groups.get({ groupId });
} catch (error) {
throw new Error(`Failed to get group: ${error.message}`);
}
};
// List all groups with optional filtering by name, offset, and limit
export const listGroups = async (name?: string, offset?: number, limit?: number) => {
try {
const query = pinata.groups.list();
if (name) query.name(name);
if (offset) query.offset(offset);
if (limit) query.limit(limit);
return await query;
} catch (error) {
throw new Error(`Failed to list groups: ${error.message}`);
}
};
// Update the name of a group
export const updateGroup = async (groupId: string, name: string) => {
try {
return await pinata.groups.update({ groupId, name });
} catch (error) {
throw new Error(`Failed to update group: ${error.message}`);
}
};
// Delete a group by ID
export const deleteGroup = async (groupId: string) => {
try {
await pinata.groups.delete({ groupId });
} catch (error) {
throw new Error(`Failed to delete group: ${error.message}`);
}
};
// Add CIDs to a group
export const addCidsToGroup = async (groupId: string, cids: string[]) => {
try {
await pinata.groups.addCids({ groupId, cids });
} catch (error) {
throw new Error(`Failed to add CIDs to group: ${error.message}`);
}
};
// Remove CIDs from a group
export const removeCidsFromGroup = async (groupId: string, cids: string[]) => {
try {
await pinata.groups.removeCids({ groupId, cids });
} catch (error) {
throw new Error(`Failed to remove CIDs from group: ${error.message}`);
}
};
;
- In our
photoController.ts
, here's how we're handling photo sharing:
export const sharePhoto = async (req: Request, res: Response) => {
const { photoId, shareType, recipientEmails } = req.body;
try {
const { data: photo, error } = await supabase
.from('photos')
.select('*')
.eq('id', photoId)
.single();
if (error) throw error;
if (photo.user_id !== (req as any).user.userId) {
return res.status(403).json({ error: 'Not authorized to share this photo' });
}
const shareLink = `${config.pinataGateway}/ipfs/${photo.ipfs_hash}`;
let expirationDate = null;
let viewCount = null;
switch (shareType) {
case 'public':
break;
case 'restricted':
if (!recipientEmails || recipientEmails.length === 0) {
return res.status(400).json({ error: 'Recipient emails are required for restricted sharing' });
}
break;
case 'view-once':
viewCount = 1;
break;
case 'time-limited':
expirationDate = new Date(Date.now() + 24 * 60 * 60 * 1000); // 24 hours from now
break;
default:
return res.status(400).json({ error: 'Invalid share type' });
}
const { data: share, error: shareError } = await supabase
.from('shares')
.insert({
photo_id: photoId,
share_type: shareType,
share_link: shareLink,
recipient_emails: recipientEmails,
expiration_date: expirationDate,
view_count: viewCount,
remaining_views: viewCount,
});
if (shareError) throw shareError;
res.json({ shareLink, share });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
};
- Organizing Photos with Pinata Groups
export const addCidsToGroup = async (groupId: string, cids: string[]) => {
return await pinata.groups.addCids({ groupId, cids });
};
This allows users to organize their photos into custom collections like "Vacation 2023" or "Family Portraits".
- Dashboard Integration We created a dashboard that leverages Pinata's group and file management:
export const getDashboardData = async (req: Request, res: Response) => {
// Fetch photos with their tags and groups
const { data: photos } = await supabase
.from('photos')
.select(`*, tags (name), groups (name)`)
.eq('user_id', userId);
// ... aggregate data for dashboard
res.json({ photos, photoCount, recentUploads });
};
What's Next?
We're continuing to develop Pixxsha, focusing on enhancing user experience and adding more features. Stay tuned for updates, and feel free to reach out if you'd like to be part of our journey!
Top comments (2)
Amazing
Thank you!