diff --git a/pages/api/auth/register.js b/pages/api/auth/register.js index dcf6c47..708416d 100644 --- a/pages/api/auth/register.js +++ b/pages/api/auth/register.js @@ -1,6 +1,7 @@ import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; import { sql } from '@vercel/postgres'; +import { generateUniqueSlug } from '../../../lib/slug-utils.js'; const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-in-production'; @@ -52,6 +53,56 @@ export default async function handler(req, res) { const user = result.rows[0]; + // Create the automatic "All My Cards" collection for the new user + try { + // Get existing slugs to ensure uniqueness + const existingSlugsData = await sql`SELECT slug FROM collections WHERE slug IS NOT NULL`; + const existingSlugs = (existingSlugsData.rows || []).map(row => row.slug); + + // Generate unique slug for "All My Cards" + const uniqueSlug = await generateUniqueSlug("All My Cards", existingSlugs); + + // Create the special collection + const collectionResult = await sql` + INSERT INTO collections ( + name, + description, + tcg, + is_public, + user_id, + slug, + is_system_collection, + created_at, + updated_at + ) + VALUES ( + 'All My Cards', + 'Automatically contains all cards you mark as owned. This collection cannot be deleted or made public.', + 'All', + false, + ${user.id}, + ${uniqueSlug}, + true, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) + RETURNING id + `; + + const collection = collectionResult.rows[0]; + + // Create owner permission for the collection + await sql` + INSERT INTO collection_permissions (collection_id, user_id, role, status, created_at) + VALUES (${collection.id}, ${user.id}, 'owner', 'active', CURRENT_TIMESTAMP) + `; + + console.log(`ā Created "All My Cards" collection for user ${user.email} (ID: ${collection.id})`); + } catch (collectionError) { + console.error('Error creating "All My Cards" collection:', collectionError); + // Don't fail the registration if collection creation fails + } + // Generate JWT token const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, diff --git a/pages/api/collections/[identifier].js b/pages/api/collections/[identifier].js index df59031..0c9a015 100644 --- a/pages/api/collections/[identifier].js +++ b/pages/api/collections/[identifier].js @@ -59,7 +59,6 @@ export default async function handler(req, res) { GROUP BY c.id, u.email, cp.role `; } else { - // Numeric ID lookup const numericId = parseInt(identifier); collectionQuery = sql` SELECT DISTINCT @@ -88,13 +87,13 @@ export default async function handler(req, res) { `; } - const result = await collectionQuery; + const collectionResult = await collectionQuery; - if (result.rows.length === 0) { + if (collectionResult.rows.length === 0) { return res.status(404).json({ error: 'Collection not found or access denied' }); } - const collection = result.rows[0]; + const collection = collectionResult.rows[0]; if (req.method === 'GET') { const formattedCollection = { @@ -108,6 +107,7 @@ export default async function handler(req, res) { lastViewed: collection.updated_at, createdAt: collection.created_at, isPublic: collection.is_public || false, + isSystemCollection: collection.is_system_collection || false, image: collection.image, tags: collection.tags ? collection.tags.split(',') : [], creator: collection.creator_email, @@ -124,6 +124,16 @@ export default async function handler(req, res) { const { name, description, isPublic, image, tags } = req.body; + // Prevent system collections from being made public + if (collection.is_system_collection && isPublic === true) { + return res.status(403).json({ error: 'System collections cannot be made public' }); + } + + // Prevent renaming system collections + if (collection.is_system_collection && name !== undefined && name !== collection.name) { + return res.status(403).json({ error: 'System collections cannot be renamed' }); + } + // If name is being changed, generate new slug let updateFields = []; let updateValues = []; @@ -187,6 +197,7 @@ export default async function handler(req, res) { name: updateResult.rows[0].name, description: updateResult.rows[0].description, isPublic: updateResult.rows[0].is_public, + isSystemCollection: updateResult.rows[0].is_system_collection, image: updateResult.rows[0].image, tags: updateResult.rows[0].tags ? updateResult.rows[0].tags.split(',') : [] }; @@ -199,6 +210,11 @@ export default async function handler(req, res) { return res.status(403).json({ error: 'Only collection owners can delete collections' }); } + // Prevent deletion of system collections + if (collection.is_system_collection) { + return res.status(403).json({ error: 'System collections cannot be deleted' }); + } + // Delete collection and all related data await sql`DELETE FROM collection_cards WHERE collection_id = ${collection.id}`; await sql`DELETE FROM collection_permissions WHERE collection_id = ${collection.id}`; diff --git a/pages/collections.js b/pages/collections.js index 0748e7b..23b814e 100644 --- a/pages/collections.js +++ b/pages/collections.js @@ -491,42 +491,52 @@ export default function Collections() { {/* Header with name and description - more space */}
diff --git a/scripts/add-system-collection-column.js b/scripts/add-system-collection-column.js new file mode 100644 index 0000000..fb31c21 --- /dev/null +++ b/scripts/add-system-collection-column.js @@ -0,0 +1,111 @@ +#!/usr/bin/env node + +import dotenv from 'dotenv'; +import { neon } from '@neondatabase/serverless'; +import { generateUniqueSlug } from '../lib/slug-utils.js'; + +dotenv.config({ path: '.env.local' }); + +async function addSystemCollectionColumn() { + const sql = neon(process.env.POSTGRES_URL); + + try { + console.log('š§ Adding is_system_collection column to collections table...'); + + // Add the is_system_collection column + await sql` + ALTER TABLE collections + ADD COLUMN IF NOT EXISTS is_system_collection BOOLEAN DEFAULT false + `; + + console.log('ā Added is_system_collection column'); + + // Get all existing users who don't have an "All My Cards" collection + console.log('š„ Finding users without "All My Cards" collection...'); + + const usersWithoutAllMyCards = await sql` + SELECT u.id, u.email + FROM users u + WHERE NOT EXISTS ( + SELECT 1 FROM collections c + WHERE c.user_id = u.id + AND c.name = 'All My Cards' + AND c.is_system_collection = true + ) + `; + + console.log(`Found ${usersWithoutAllMyCards.length} users without "All My Cards" collection`); + + // Get existing slugs for uniqueness + const existingSlugsData = await sql`SELECT slug FROM collections WHERE slug IS NOT NULL`; + const existingSlugs = (existingSlugsData || []).map(row => row.slug); + + // Create "All My Cards" collection for each user + for (const user of usersWithoutAllMyCards) { + try { + console.log(`Creating "All My Cards" for ${user.email}...`); + + // Generate unique slug + const uniqueSlug = await generateUniqueSlug("All My Cards", existingSlugs); + existingSlugs.push(uniqueSlug); + + // Create the collection + const collectionResult = await sql` + INSERT INTO collections ( + name, + description, + tcg, + is_public, + user_id, + slug, + is_system_collection, + created_at, + updated_at + ) + VALUES ( + 'All My Cards', + 'Automatically contains all cards you mark as owned. This collection cannot be deleted or made public.', + 'All', + false, + ${user.id}, + ${uniqueSlug}, + true, + CURRENT_TIMESTAMP, + CURRENT_TIMESTAMP + ) + RETURNING id + `; + + const collection = collectionResult[0]; + + // Create owner permission + await sql` + INSERT INTO collection_permissions (collection_id, user_id, role, status, created_at) + VALUES (${collection.id}, ${user.id}, 'owner', 'active', CURRENT_TIMESTAMP) + `; + + console.log(` ā Created collection ID ${collection.id} with slug "${uniqueSlug}"`); + + } catch (error) { + console.error(` ā Failed to create collection for ${user.email}:`, error.message); + } + } + + console.log('\nš Migration completed successfully!'); + console.log('\nš Summary:'); + console.log(` ⢠Added is_system_collection column to collections table`); + console.log(` ⢠Created "All My Cards" collections for ${usersWithoutAllMyCards.length} existing users`); + console.log(` ⢠New users will automatically get this collection on registration`); + + } catch (error) { + console.error('ā Migration failed:', error.message); + console.error('Full error:', error); + process.exit(1); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + addSystemCollectionColumn(); +} + +export { addSystemCollectionColumn }; \ No newline at end of file