deckhearth/pages/api/collections/[id].js
Randall Stillwell b917972f5e 🔄 Simplified Permission Model & UX Improvements
🎯 Cleaner Permission System:
- Collections are private/invite-only by default
- Public toggle only controls community visibility (not edit permissions)
- Simplified to: Creator + Invited Collaborators can edit, everyone else view-only
- Only creator can delete collections

📝 UI/UX Enhancements:
- Changed 'Invite User' to 'Invite Collaborator' with clearer messaging
- Replaced visibility dropdown with clean public/private toggle
- Updated role descriptions: 'Collaborator' (editor) and 'Viewer'
- Default invitation role is now 'editor' (collaborator)
- Added explanatory text about collaboration purpose

🔐 Permission Logic Updates:
- Public collections: visible in community but invite-only editing
- Private collections: hidden from community, invite-only editing
- Removed complex visibility states (invite-only/private distinction)
- Updated permission middleware for simplified model

📧 Email Template Updates:
- Clearer role descriptions in invitation emails
- Focus on collaboration and card management permissions
- Removed owner role from invitation options

🗄️ Database Schema Updates:
- Updated APIs to use is_public boolean instead of visibility enum
- Maintained backward compatibility with existing data
- Simplified permission checking logic

The system now has a much cleaner UX: collections are collaborative workspaces that can optionally be made visible to the community! 🚀
2025-07-25 10:29:45 -05:00

132 lines
No EOL
3.8 KiB
JavaScript

import { sql } from '@vercel/postgres';
import { withCollectionPermission, getUserFromRequest, logCollectionActivity } from '../../../lib/permission-middleware';
async function handler(req, res) {
// Set CORS headers
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Handle preflight requests
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
const { id } = req.query;
if (req.method === 'GET') {
// GET requests use the permission from middleware
const collection = req.permission.collection;
const userRole = req.permission.role;
try {
// Get detailed collection info with creator
const collectionResult = await sql`
SELECT
c.*,
u.email as creator_email
FROM collections c
LEFT JOIN users u ON c.user_id = u.id
WHERE c.id = ${id}
`;
const collectionDetails = collectionResult.rows[0];
// Get cards in the collection
const cardsResult = await sql`
SELECT
cc.*,
cards.name,
cards.set_name,
cards.rarity,
cards.type,
cards.image_url,
cards.market_price
FROM collection_cards cc
JOIN cards ON cc.card_id = cards.id
WHERE cc.collection_id = ${id}
ORDER BY cc.created_at ASC
`;
const cards = cardsResult.rows;
// Calculate collection stats
const totalCards = cards.reduce((sum, card) => sum + card.quantity, 0);
const totalValue = cards.reduce((sum, card) => sum + (card.market_price * card.quantity), 0);
res.status(200).json({
collection: {
...collectionDetails,
totalCards,
totalValue,
userRole
},
cards
});
} catch (error) {
console.error('Error fetching collection:', error);
res.status(500).json({ error: 'Internal server error' });
}
} else if (req.method === 'PUT') {
// Update collection - requires editor permissions
try {
const { name, description, isPublic } = req.body;
const result = await sql`
UPDATE collections
SET
name = ${name},
description = ${description},
is_public = ${isPublic},
updated_at = NOW()
WHERE id = ${id}
RETURNING *
`;
// Log activity
await logCollectionActivity(id, req.user.userId, 'collection_updated', {
name,
description,
isPublic
});
res.status(200).json(result.rows[0]);
} catch (error) {
console.error('Error updating collection:', error);
res.status(500).json({ error: 'Internal server error' });
}
} else if (req.method === 'DELETE') {
// Delete collection - requires owner permissions
try {
// Log activity before deletion
await logCollectionActivity(id, req.user.userId, 'collection_deleted', {});
// Delete collection (cascade will handle related records)
const result = await sql`DELETE FROM collections WHERE id = ${id} RETURNING *`;
res.status(200).json({ message: 'Collection deleted successfully' });
} catch (error) {
console.error('Error deleting collection:', error);
res.status(500).json({ error: 'Internal server error' });
}
} else {
res.status(405).json({ error: 'Method not allowed' });
}
}
// Apply permission middleware based on method
export default async function(req, res) {
let requiredPermission = 'viewer'; // Default for GET
if (req.method === 'PUT') {
requiredPermission = 'editor';
} else if (req.method === 'DELETE') {
requiredPermission = 'owner';
}
return withCollectionPermission(requiredPermission)(handler)(req, res);
};