diff --git a/pages/api/cards/[id]/ownership.js b/pages/api/cards/[id]/ownership.js index 0b4c84c..cf1405e 100644 --- a/pages/api/cards/[id]/ownership.js +++ b/pages/api/cards/[id]/ownership.js @@ -2,7 +2,19 @@ import { sql } from '@vercel/postgres'; import { getUserFromRequest } from '../../../../lib/permission-middleware'; export default async function handler(req, res) { - const { id } = req.query; + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + if (req.method === 'OPTIONS') { + res.status(200).end(); + return; + } + + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } try { // Get authenticated user @@ -11,75 +23,95 @@ export default async function handler(req, res) { return res.status(401).json({ error: 'Authentication required' }); } - if (req.method === 'GET') { - // Get user's ownership of this card + const { id } = req.query; + const { quantity } = req.body; + + if (!id || quantity === undefined) { + return res.status(400).json({ error: 'Card ID and quantity are required' }); + } + + const cardId = parseInt(id); + const cardQuantity = parseInt(quantity); + + if (isNaN(cardId) || isNaN(cardQuantity) || cardQuantity < 0) { + return res.status(400).json({ error: 'Invalid card ID or quantity' }); + } + + // Verify the card exists + const cardCheck = await sql`SELECT id, name FROM cards WHERE id = ${cardId}`; + if (cardCheck.rows.length === 0) { + return res.status(404).json({ error: 'Card not found' }); + } + + const card = cardCheck.rows[0]; + + // Find the user's "All My Cards" collection + const allMyCardsCollection = await sql` + SELECT id FROM collections + WHERE user_id = ${user.userId} + AND name = 'All My Cards' + AND is_system_collection = true + `; + + if (allMyCardsCollection.rows.length === 0) { + return res.status(500).json({ error: 'All My Cards collection not found' }); + } + + const collectionId = allMyCardsCollection.rows[0].id; + + if (cardQuantity > 0) { + // Insert or update user's card ownership const result = await sql` - SELECT uc.quantity - FROM user_cards uc - WHERE uc.user_id = ${user.userId} AND uc.card_id = ${id} + INSERT INTO user_cards (user_id, card_id, quantity, created_at, updated_at) + VALUES (${user.userId}, ${cardId}, ${cardQuantity}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + ON CONFLICT (user_id, card_id) + DO UPDATE SET + quantity = ${cardQuantity}, + updated_at = CURRENT_TIMESTAMP + RETURNING * `; - const quantity = result.rows.length > 0 ? result.rows[0].quantity : 0; + // Sync with "All My Cards" collection + const collectionCardResult = await sql` + INSERT INTO collection_cards (collection_id, card_id, quantity, created_at, updated_at) + VALUES (${collectionId}, ${cardId}, ${cardQuantity}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + ON CONFLICT (collection_id, card_id) + DO UPDATE SET + quantity = ${cardQuantity}, + updated_at = CURRENT_TIMESTAMP + RETURNING * + `; res.status(200).json({ success: true, - quantity: quantity + message: 'Card ownership updated and synced to All My Cards collection', + card: { + id: card.id, + name: card.name, + quantity: result.rows[0].quantity + } }); - } else if (req.method === 'POST') { - const { quantity } = req.body; - // Check if card exists - const cardCheck = await sql` - SELECT id, name FROM cards WHERE id = ${id} - `; - - if (cardCheck.rows.length === 0) { - return res.status(404).json({ error: 'Card not found' }); - } - - const card = cardCheck.rows[0]; - - if (quantity > 0) { - // Insert or update user's card ownership - const result = await sql` - INSERT INTO user_cards (user_id, card_id, quantity) - VALUES (${user.userId}, ${id}, ${quantity}) - ON CONFLICT (user_id, card_id) - DO UPDATE SET - quantity = ${quantity}, - updated_at = CURRENT_TIMESTAMP - RETURNING * - `; - - res.status(200).json({ - success: true, - card: { - id: card.id, - name: card.name, - quantity: result.rows[0].quantity - } - }); - } else { - // Remove card from user's collection if quantity is 0 - await sql` - DELETE FROM user_cards - WHERE user_id = ${user.userId} AND card_id = ${id} - `; - - res.status(200).json({ - success: true, - card: { - id: card.id, - name: card.name, - quantity: 0 - } - }); - } } else { - res.status(405).json({ error: 'Method not allowed' }); + // Remove card ownership + await sql`DELETE FROM user_cards WHERE user_id = ${user.userId} AND card_id = ${cardId}`; + + // Remove from "All My Cards" collection + await sql`DELETE FROM collection_cards WHERE collection_id = ${collectionId} AND card_id = ${cardId}`; + + res.status(200).json({ + success: true, + message: 'Card ownership removed and synced from All My Cards collection', + card: { + id: card.id, + name: card.name, + quantity: 0 + } + }); } + } catch (error) { console.error('Error handling ownership:', error); - res.status(500).json({ error: 'Failed to handle ownership' }); + res.status(500).json({ error: 'Internal server error' }); } } \ No newline at end of file diff --git a/scripts/fix-user-cards-constraints.js b/scripts/fix-user-cards-constraints.js new file mode 100644 index 0000000..425ab5f --- /dev/null +++ b/scripts/fix-user-cards-constraints.js @@ -0,0 +1,168 @@ +#!/usr/bin/env node + +import dotenv from 'dotenv'; +import { neon } from '@neondatabase/serverless'; + +dotenv.config({ path: '.env.local' }); + +async function fixUserCardsConstraints() { + const sql = neon(process.env.POSTGRES_URL); + + try { + console.log('šŸ”§ Fixing user_cards table constraints...'); + + // First, check if there are any duplicate entries that would prevent adding the constraint + console.log('šŸ” Checking for duplicate entries...'); + const duplicates = await sql` + SELECT user_id, card_id, COUNT(*) as count + FROM user_cards + GROUP BY user_id, card_id + HAVING COUNT(*) > 1 + `; + + if (duplicates.length > 0) { + console.log(`āš ļø Found ${duplicates.length} duplicate entries. Cleaning up...`); + + // Remove duplicates by keeping the latest entry for each user/card combination + for (const dup of duplicates) { + console.log(`Cleaning duplicates for user ${dup.user_id}, card ${dup.card_id}...`); + + // Keep only the most recent entry + await sql` + DELETE FROM user_cards + WHERE user_id = ${dup.user_id} AND card_id = ${dup.card_id} + AND id NOT IN ( + SELECT id FROM user_cards + WHERE user_id = ${dup.user_id} AND card_id = ${dup.card_id} + ORDER BY updated_at DESC, id DESC + LIMIT 1 + ) + `; + } + console.log('āœ… Cleaned up duplicate entries'); + } else { + console.log('āœ… No duplicate entries found'); + } + + // Add the unique constraint to user_cards + console.log('šŸ”’ Adding unique constraint on user_cards (user_id, card_id)...'); + try { + await sql` + ALTER TABLE user_cards + ADD CONSTRAINT user_cards_user_card_unique + UNIQUE (user_id, card_id) + `; + console.log('āœ… Added user_cards unique constraint'); + } catch (error) { + if (error.message.includes('already exists')) { + console.log('āœ… User_cards unique constraint already exists'); + } else { + throw error; + } + } + + // Add the unique constraint to collection_cards + console.log('šŸ”’ Adding unique constraint on collection_cards (collection_id, card_id)...'); + try { + await sql` + ALTER TABLE collection_cards + ADD CONSTRAINT collection_cards_collection_card_unique + UNIQUE (collection_id, card_id) + `; + console.log('āœ… Added collection_cards unique constraint'); + } catch (error) { + if (error.message.includes('already exists')) { + console.log('āœ… Collection_cards unique constraint already exists'); + } else { + throw error; + } + } + + // Now sync existing owned cards to "All My Cards" collections + console.log('šŸ”„ Syncing owned cards to "All My Cards" collections...'); + + // Get all users with owned cards + const usersWithOwnedCards = await sql` + SELECT DISTINCT uc.user_id, u.email + FROM user_cards uc + JOIN users u ON uc.user_id = u.id + WHERE uc.quantity > 0 + `; + + console.log(`Found ${usersWithOwnedCards.length} users with owned cards`); + + for (const user of usersWithOwnedCards) { + try { + // Find the user's "All My Cards" collection + const allMyCardsCollection = await sql` + SELECT id FROM collections + WHERE user_id = ${user.user_id} + AND name = 'All My Cards' + AND is_system_collection = true + `; + + if (allMyCardsCollection.length === 0) { + console.log(`āš ļø No "All My Cards" collection found for ${user.email}`); + continue; + } + + const collectionId = allMyCardsCollection[0].id; + + // Get all owned cards for this user + const ownedCards = await sql` + SELECT card_id, quantity + FROM user_cards + WHERE user_id = ${user.user_id} AND quantity > 0 + `; + + console.log(`Syncing ${ownedCards.length} owned cards for ${user.email}...`); + + // Add each owned card to the "All My Cards" collection + for (const ownedCard of ownedCards) { + // Check if card is already in the collection + const existingEntry = await sql` + SELECT id, quantity FROM collection_cards + WHERE collection_id = ${collectionId} AND card_id = ${ownedCard.card_id} + `; + + if (existingEntry.length > 0) { + // Update existing entry + await sql` + UPDATE collection_cards + SET quantity = ${ownedCard.quantity}, updated_at = CURRENT_TIMESTAMP + WHERE collection_id = ${collectionId} AND card_id = ${ownedCard.card_id} + `; + } else { + // Insert new entry + await sql` + INSERT INTO collection_cards (collection_id, card_id, quantity, created_at, updated_at) + VALUES (${collectionId}, ${ownedCard.card_id}, ${ownedCard.quantity}, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) + `; + } + } + + console.log(` āœ… Synced ${ownedCards.length} cards for ${user.email}`); + + } catch (error) { + console.error(` āŒ Failed to sync cards for ${user.email}:`, error.message); + } + } + + console.log('\nšŸŽ‰ Migration completed successfully!'); + console.log('\nšŸ“‹ Summary:'); + console.log(` • Fixed unique constraint on user_cards table`); + console.log(` • Synced owned cards to "All My Cards" collections`); + console.log(` • Card ownership API should now work properly`); + + } catch (error) { + console.error('āŒ Migration failed:', error.message); + console.error('Full error:', error); + process.exit(1); + } +} + +if (import.meta.url === `file://${process.argv[1]}`) { + fixUserCardsConstraints(); +} + +export { fixUserCardsConstraints }; \ No newline at end of file