diff --git a/api/user-cards.js b/api/user-cards.js new file mode 100644 index 0000000..5fbb920 --- /dev/null +++ b/api/user-cards.js @@ -0,0 +1,429 @@ +import { NextResponse } from 'next/server'; +import { sql } from '@vercel/postgres'; +import { verifyToken } from './setup-auth.js'; + +// GET /api/user-cards - Get user's cards with optional filters +export async function GET(request) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { searchParams } = new URL(request.url); + const game = searchParams.get('game'); + const rarity = searchParams.get('rarity'); + const status = searchParams.get('status'); + const search = searchParams.get('search'); + + let query = ` + SELECT + uc.id, + uc.user_id, + uc.card_id, + uc.status, + uc.quantity, + uc.condition, + uc.notes, + uc.acquired_date, + uc.acquired_price, + uc.acquired_from, + uc.created_at, + uc.updated_at, + c.name, + c.set_name, + c.set_code, + c.card_number, + c.rarity, + c.game, + c.card_type, + c.mana_cost, + c.cmc, + c.colors, + c.oracle_text, + c.power, + c.toughness, + c.image_url, + c.stock_image_url, + c.current_price, + c.market_price, + c.verified, + c.created_at as card_created_at, + c.updated_at as card_updated_at + FROM user_cards uc + JOIN cards c ON uc.card_id = c.id + WHERE uc.user_id = $1 + `; + + const params = [user.id]; + let paramIndex = 2; + + if (game) { + query += ` AND c.game = $${paramIndex}`; + params.push(game); + paramIndex++; + } + + if (rarity) { + query += ` AND c.rarity = $${paramIndex}`; + params.push(rarity); + paramIndex++; + } + + if (status && status !== 'all') { + query += ` AND uc.status = $${paramIndex}`; + params.push(status); + paramIndex++; + } + + if (search) { + query += ` AND (c.name ILIKE $${paramIndex} OR c.set_name ILIKE $${paramIndex})`; + params.push(`%${search}%`); + paramIndex++; + } + + query += ` ORDER BY uc.created_at DESC`; + + const result = await sql.query(query, params); + + const userCards = result.rows.map(row => ({ + id: row.id, + userId: row.user_id, + cardId: row.card_id, + status: row.status, + quantity: row.quantity, + condition: row.condition, + notes: row.notes, + acquiredDate: row.acquired_date, + acquiredPrice: row.acquired_price, + acquiredFrom: row.acquired_from, + createdAt: row.created_at, + updatedAt: row.updated_at, + card: { + id: row.card_id, + name: row.name, + set_name: row.set_name, + set_code: row.set_code, + card_number: row.card_number, + rarity: row.rarity, + game: row.game, + card_type: row.card_type, + mana_cost: row.mana_cost, + cmc: row.cmc, + colors: row.colors ? JSON.parse(row.colors) : [], + oracle_text: row.oracle_text, + power: row.power, + toughness: row.toughness, + image_url: row.image_url, + stock_image_url: row.stock_image_url, + current_price: row.current_price, + market_price: row.market_price, + verified: row.verified, + createdAt: row.card_created_at, + updatedAt: row.card_updated_at, + } + })); + + return NextResponse.json({ + success: true, + data: userCards, + message: `Found ${userCards.length} cards` + }); + + } catch (error) { + console.error('Error fetching user cards:', error); + return NextResponse.json( + { error: 'Failed to fetch user cards' }, + { status: 500 } + ); + } +} + +// POST /api/user-cards - Add card to user collection +export async function POST(request) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const body = await request.json(); + const { + cardId, + status = 'owned', + quantity = 1, + condition, + notes, + acquiredDate, + acquiredPrice, + acquiredFrom, + collectionIds = [], + deckIds = [] + } = body; + + if (!cardId) { + return NextResponse.json( + { error: 'Card ID is required' }, + { status: 400 } + ); + } + + // Check if card exists + const cardResult = await sql.query( + 'SELECT id FROM cards WHERE id = $1', + [cardId] + ); + + if (cardResult.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found' }, + { status: 404 } + ); + } + + // Check if user already has this card + const existingResult = await sql.query( + 'SELECT id FROM user_cards WHERE user_id = $1 AND card_id = $2', + [user.id, cardId] + ); + + if (existingResult.rows.length > 0) { + return NextResponse.json( + { error: 'Card already in collection' }, + { status: 409 } + ); + } + + // Add card to user collection + const result = await sql.query( + `INSERT INTO user_cards ( + user_id, card_id, status, quantity, condition, notes, + acquired_date, acquired_price, acquired_from, created_at, updated_at + ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW()) + RETURNING *`, + [ + user.id, cardId, status, quantity, condition, notes, + acquiredDate, acquiredPrice, acquiredFrom + ] + ); + + const userCard = result.rows[0]; + + // Add to collections if specified + if (collectionIds.length > 0) { + for (const collectionId of collectionIds) { + await sql.query( + 'INSERT INTO collection_cards (collection_id, user_card_id) VALUES ($1, $2)', + [collectionId, userCard.id] + ); + } + } + + // Add to decks if specified + if (deckIds.length > 0) { + for (const deckId of deckIds) { + await sql.query( + 'INSERT INTO deck_cards (deck_id, user_card_id, quantity, board) VALUES ($1, $2, $3, $4)', + [deckId, userCard.id, quantity, 'mainboard'] + ); + } + } + + return NextResponse.json({ + success: true, + data: { + id: userCard.id, + userId: userCard.user_id, + cardId: userCard.card_id, + status: userCard.status, + quantity: userCard.quantity, + condition: userCard.condition, + notes: userCard.notes, + acquiredDate: userCard.acquired_date, + acquiredPrice: userCard.acquired_price, + acquiredFrom: userCard.acquired_from, + createdAt: userCard.created_at, + updatedAt: userCard.updated_at, + }, + message: 'Card added to collection' + }); + + } catch (error) { + console.error('Error adding card to collection:', error); + return NextResponse.json( + { error: 'Failed to add card to collection' }, + { status: 500 } + ); + } +} + +// PUT /api/user-cards/[id] - Update user card +export async function PUT(request, { params }) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id } = params; + const body = await request.json(); + + // Check if user owns this card + const ownershipResult = await sql.query( + 'SELECT id FROM user_cards WHERE id = $1 AND user_id = $2', + [id, user.id] + ); + + if (ownershipResult.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found or not owned by user' }, + { status: 404 } + ); + } + + // Build update query dynamically + const updateFields = []; + const updateValues = []; + let paramIndex = 1; + + if (body.status !== undefined) { + updateFields.push(`status = $${paramIndex}`); + updateValues.push(body.status); + paramIndex++; + } + + if (body.quantity !== undefined) { + updateFields.push(`quantity = $${paramIndex}`); + updateValues.push(body.quantity); + paramIndex++; + } + + if (body.condition !== undefined) { + updateFields.push(`condition = $${paramIndex}`); + updateValues.push(body.condition); + paramIndex++; + } + + if (body.notes !== undefined) { + updateFields.push(`notes = $${paramIndex}`); + updateValues.push(body.notes); + paramIndex++; + } + + if (body.acquiredDate !== undefined) { + updateFields.push(`acquired_date = $${paramIndex}`); + updateValues.push(body.acquiredDate); + paramIndex++; + } + + if (body.acquiredPrice !== undefined) { + updateFields.push(`acquired_price = $${paramIndex}`); + updateValues.push(body.acquiredPrice); + paramIndex++; + } + + if (body.acquiredFrom !== undefined) { + updateFields.push(`acquired_from = $${paramIndex}`); + updateValues.push(body.acquiredFrom); + paramIndex++; + } + + if (updateFields.length === 0) { + return NextResponse.json( + { error: 'No fields to update' }, + { status: 400 } + ); + } + + updateFields.push(`updated_at = NOW()`); + updateValues.push(id); + + const query = ` + UPDATE user_cards + SET ${updateFields.join(', ')} + WHERE id = $${paramIndex} + RETURNING * + `; + + const result = await sql.query(query, updateValues); + const userCard = result.rows[0]; + + return NextResponse.json({ + success: true, + data: { + id: userCard.id, + userId: userCard.user_id, + cardId: userCard.card_id, + status: userCard.status, + quantity: userCard.quantity, + condition: userCard.condition, + notes: userCard.notes, + acquiredDate: userCard.acquired_date, + acquiredPrice: userCard.acquired_price, + acquiredFrom: userCard.acquired_from, + createdAt: userCard.created_at, + updatedAt: userCard.updated_at, + }, + message: 'Card updated successfully' + }); + + } catch (error) { + console.error('Error updating user card:', error); + return NextResponse.json( + { error: 'Failed to update card' }, + { status: 500 } + ); + } +} + +// DELETE /api/user-cards/[id] - Delete user card +export async function DELETE(request, { params }) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id } = params; + + // Check if user owns this card + const ownershipResult = await sql.query( + 'SELECT id FROM user_cards WHERE id = $1 AND user_id = $2', + [id, user.id] + ); + + if (ownershipResult.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found or not owned by user' }, + { status: 404 } + ); + } + + // Delete from collections and decks first + await sql.query('DELETE FROM collection_cards WHERE user_card_id = $1', [id]); + await sql.query('DELETE FROM deck_cards WHERE user_card_id = $1', [id]); + + // Delete the user card + await sql.query('DELETE FROM user_cards WHERE id = $1', [id]); + + return NextResponse.json({ + success: true, + message: 'Card removed from collection' + }); + + } catch (error) { + console.error('Error deleting user card:', error); + return NextResponse.json( + { error: 'Failed to delete card' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/api/user-cards/[id].js b/api/user-cards/[id].js new file mode 100644 index 0000000..22fb8d8 --- /dev/null +++ b/api/user-cards/[id].js @@ -0,0 +1,282 @@ +import { NextResponse } from 'next/server'; +import { sql } from '@vercel/postgres'; +import { verifyToken } from '../setup-auth.js'; + +// GET /api/user-cards/[id] - Get single user card +export async function GET(request, { params }) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id } = params; + + const result = await sql.query( + `SELECT + uc.id, + uc.user_id, + uc.card_id, + uc.status, + uc.quantity, + uc.condition, + uc.notes, + uc.acquired_date, + uc.acquired_price, + uc.acquired_from, + uc.created_at, + uc.updated_at, + c.name, + c.set_name, + c.set_code, + c.card_number, + c.rarity, + c.game, + c.card_type, + c.mana_cost, + c.cmc, + c.colors, + c.oracle_text, + c.power, + c.toughness, + c.image_url, + c.stock_image_url, + c.current_price, + c.market_price, + c.verified, + c.created_at as card_created_at, + c.updated_at as card_updated_at + FROM user_cards uc + JOIN cards c ON uc.card_id = c.id + WHERE uc.id = $1 AND uc.user_id = $2`, + [id, user.id] + ); + + if (result.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found or not owned by user' }, + { status: 404 } + ); + } + + const row = result.rows[0]; + const userCard = { + id: row.id, + userId: row.user_id, + cardId: row.card_id, + status: row.status, + quantity: row.quantity, + condition: row.condition, + notes: row.notes, + acquiredDate: row.acquired_date, + acquiredPrice: row.acquired_price, + acquiredFrom: row.acquired_from, + createdAt: row.created_at, + updatedAt: row.updated_at, + card: { + id: row.card_id, + name: row.name, + set_name: row.set_name, + set_code: row.set_code, + card_number: row.card_number, + rarity: row.rarity, + game: row.game, + card_type: row.card_type, + mana_cost: row.mana_cost, + cmc: row.cmc, + colors: row.colors ? JSON.parse(row.colors) : [], + oracle_text: row.oracle_text, + power: row.power, + toughness: row.toughness, + image_url: row.image_url, + stock_image_url: row.stock_image_url, + current_price: row.current_price, + market_price: row.market_price, + verified: row.verified, + createdAt: row.card_created_at, + updatedAt: row.card_updated_at, + } + }; + + return NextResponse.json({ + success: true, + data: userCard + }); + + } catch (error) { + console.error('Error fetching user card:', error); + return NextResponse.json( + { error: 'Failed to fetch user card' }, + { status: 500 } + ); + } +} + +// PUT /api/user-cards/[id] - Update user card +export async function PUT(request, { params }) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id } = params; + const body = await request.json(); + + // Check if user owns this card + const ownershipResult = await sql.query( + 'SELECT id FROM user_cards WHERE id = $1 AND user_id = $2', + [id, user.id] + ); + + if (ownershipResult.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found or not owned by user' }, + { status: 404 } + ); + } + + // Build update query dynamically + const updateFields = []; + const updateValues = []; + let paramIndex = 1; + + if (body.status !== undefined) { + updateFields.push(`status = $${paramIndex}`); + updateValues.push(body.status); + paramIndex++; + } + + if (body.quantity !== undefined) { + updateFields.push(`quantity = $${paramIndex}`); + updateValues.push(body.quantity); + paramIndex++; + } + + if (body.condition !== undefined) { + updateFields.push(`condition = $${paramIndex}`); + updateValues.push(body.condition); + paramIndex++; + } + + if (body.notes !== undefined) { + updateFields.push(`notes = $${paramIndex}`); + updateValues.push(body.notes); + paramIndex++; + } + + if (body.acquiredDate !== undefined) { + updateFields.push(`acquired_date = $${paramIndex}`); + updateValues.push(body.acquiredDate); + paramIndex++; + } + + if (body.acquiredPrice !== undefined) { + updateFields.push(`acquired_price = $${paramIndex}`); + updateValues.push(body.acquiredPrice); + paramIndex++; + } + + if (body.acquiredFrom !== undefined) { + updateFields.push(`acquired_from = $${paramIndex}`); + updateValues.push(body.acquiredFrom); + paramIndex++; + } + + if (updateFields.length === 0) { + return NextResponse.json( + { error: 'No fields to update' }, + { status: 400 } + ); + } + + updateFields.push(`updated_at = NOW()`); + updateValues.push(id); + + const query = ` + UPDATE user_cards + SET ${updateFields.join(', ')} + WHERE id = $${paramIndex} + RETURNING * + `; + + const result = await sql.query(query, updateValues); + const userCard = result.rows[0]; + + return NextResponse.json({ + success: true, + data: { + id: userCard.id, + userId: userCard.user_id, + cardId: userCard.card_id, + status: userCard.status, + quantity: userCard.quantity, + condition: userCard.condition, + notes: userCard.notes, + acquiredDate: userCard.acquired_date, + acquiredPrice: userCard.acquired_price, + acquiredFrom: userCard.acquired_from, + createdAt: userCard.created_at, + updatedAt: userCard.updated_at, + }, + message: 'Card updated successfully' + }); + + } catch (error) { + console.error('Error updating user card:', error); + return NextResponse.json( + { error: 'Failed to update card' }, + { status: 500 } + ); + } +} + +// DELETE /api/user-cards/[id] - Delete user card +export async function DELETE(request, { params }) { + try { + const token = request.headers.get('authorization')?.replace('Bearer ', ''); + const user = await verifyToken(token); + + if (!user) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + } + + const { id } = params; + + // Check if user owns this card + const ownershipResult = await sql.query( + 'SELECT id FROM user_cards WHERE id = $1 AND user_id = $2', + [id, user.id] + ); + + if (ownershipResult.rows.length === 0) { + return NextResponse.json( + { error: 'Card not found or not owned by user' }, + { status: 404 } + ); + } + + // Delete from collections and decks first + await sql.query('DELETE FROM collection_cards WHERE user_card_id = $1', [id]); + await sql.query('DELETE FROM deck_cards WHERE user_card_id = $1', [id]); + + // Delete the user card + await sql.query('DELETE FROM user_cards WHERE id = $1', [id]); + + return NextResponse.json({ + success: true, + message: 'Card removed from collection' + }); + + } catch (error) { + console.error('Error deleting user card:', error); + return NextResponse.json( + { error: 'Failed to delete card' }, + { status: 500 } + ); + } +} \ No newline at end of file