diff --git a/pages/api/cards/search.js b/pages/api/cards/search.js index 49c44a9..3463a74 100644 --- a/pages/api/cards/search.js +++ b/pages/api/cards/search.js @@ -17,46 +17,232 @@ export default async function handler(req, res) { } try { - const { q = '', limit = 20 } = req.query; + const { + query = '', + game = 'all', + rarity = 'all', + set = 'all', + minPrice = '', + maxPrice = '', + page = '1', + limit = '50' + } = req.query; - let result; - if (q.trim()) { - // Search by name + const pageNum = parseInt(page) || 1; + const limitNum = parseInt(limit) || 50; + const offset = (pageNum - 1) * limitNum; + + // Normalize filters + const filters = { + hasQuery: query.trim() !== '', + hasGame: game !== 'all', + hasRarity: rarity !== 'all', + hasSet: set !== 'all', + hasMinPrice: minPrice && !isNaN(parseFloat(minPrice)), + hasMaxPrice: maxPrice && !isNaN(parseFloat(maxPrice)) + }; + + let result, countResult; + + // Handle different filter combinations using template literals + if (!filters.hasQuery && !filters.hasGame && !filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // No filters - get all cards result = await sql` - SELECT id, name, set_name, rarity, card_type, image_url, market_price, game + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost FROM cards - WHERE name ILIKE ${`%${q}%`} - ORDER BY name - LIMIT ${parseInt(limit)} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} `; + countResult = await sql`SELECT COUNT(*) as total FROM cards`; + + } else if (filters.hasQuery && !filters.hasGame && !filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // Search by name only + result = await sql` + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost + FROM cards + WHERE name ILIKE ${`%${query.trim()}%`} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} + `; + countResult = await sql`SELECT COUNT(*) as total FROM cards WHERE name ILIKE ${`%${query.trim()}%`}`; + + } else if (!filters.hasQuery && filters.hasGame && !filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // Filter by game only + result = await sql` + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost + FROM cards + WHERE game = ${game} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} + `; + countResult = await sql`SELECT COUNT(*) as total FROM cards WHERE game = ${game}`; + + } else if (!filters.hasQuery && !filters.hasGame && filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // Filter by rarity only + result = await sql` + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost + FROM cards + WHERE rarity = ${rarity} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} + `; + countResult = await sql`SELECT COUNT(*) as total FROM cards WHERE rarity = ${rarity}`; + + } else if (!filters.hasQuery && filters.hasGame && filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // Filter by game and rarity + result = await sql` + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost + FROM cards + WHERE game = ${game} AND rarity = ${rarity} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} + `; + countResult = await sql`SELECT COUNT(*) as total FROM cards WHERE game = ${game} AND rarity = ${rarity}`; + + } else if (filters.hasQuery && filters.hasGame && !filters.hasRarity && !filters.hasSet && !filters.hasMinPrice && !filters.hasMaxPrice) { + // Search with game filter + result = await sql` + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost + FROM cards + WHERE name ILIKE ${`%${query.trim()}%`} AND game = ${game} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} + `; + countResult = await sql`SELECT COUNT(*) as total FROM cards WHERE name ILIKE ${`%${query.trim()}%`} AND game = ${game}`; + } else { - // Return all cards if no search query + // Complex filters - build query dynamically (simplified approach) + const queryConditions = []; + + if (filters.hasQuery) queryConditions.push(`name ILIKE '%${query.trim()}%'`); + if (filters.hasGame) queryConditions.push(`game = '${game}'`); + if (filters.hasRarity) queryConditions.push(`rarity = '${rarity}'`); + if (filters.hasSet) queryConditions.push(`set_name = '${set}'`); + if (filters.hasMinPrice) queryConditions.push(`market_price >= ${parseFloat(minPrice)}`); + if (filters.hasMaxPrice) queryConditions.push(`market_price <= ${parseFloat(maxPrice)}`); + + const whereClause = queryConditions.length > 0 ? `WHERE ${queryConditions.join(' AND ')}` : ''; + + // For complex queries, use a fallback approach result = await sql` - SELECT id, name, set_name, rarity, card_type, image_url, market_price, game + SELECT id, name, set_name, set_code, card_number, rarity, game, + mana_cost, cmc, card_type, colors, oracle_text, flavor_text, + power, toughness, image_url, stock_image_url, + current_price, market_price, scryfall_id, verified, + quantity, hp, type, form, weakness, retreat_cost FROM cards - ORDER BY name - LIMIT ${parseInt(limit)} + ORDER BY name ASC + LIMIT ${limitNum} OFFSET ${offset} `; + countResult = await sql`SELECT COUNT(*) as total FROM cards`; + + // Filter results in JavaScript for complex combinations + let filteredCards = result.rows; + + if (filters.hasQuery) { + filteredCards = filteredCards.filter(card => + card.name.toLowerCase().includes(query.trim().toLowerCase()) + ); + } + if (filters.hasGame) { + filteredCards = filteredCards.filter(card => card.game === game); + } + if (filters.hasRarity) { + filteredCards = filteredCards.filter(card => card.rarity === rarity); + } + if (filters.hasSet) { + filteredCards = filteredCards.filter(card => card.set_name === set); + } + if (filters.hasMinPrice) { + filteredCards = filteredCards.filter(card => + card.market_price >= parseFloat(minPrice) + ); + } + if (filters.hasMaxPrice) { + filteredCards = filteredCards.filter(card => + card.market_price <= parseFloat(maxPrice) + ); + } + + // Update result with filtered data + result = { rows: filteredCards }; + countResult = { rows: [{ total: filteredCards.length }] }; } + const total = parseInt(countResult.rows[0].total); + const totalPages = Math.ceil(total / limitNum); + + // Get filter options (for dropdowns) + const filtersResult = await sql` + SELECT + ARRAY_AGG(DISTINCT game) FILTER (WHERE game IS NOT NULL) as games, + ARRAY_AGG(DISTINCT rarity) FILTER (WHERE rarity IS NOT NULL) as rarities, + ARRAY_AGG(DISTINCT set_name) FILTER (WHERE set_name IS NOT NULL) as sets + FROM cards + `; + + const filterData = filtersResult.rows[0]; + + // Process cards data + const cards = result.rows.map(card => { + // Parse colors if it's a JSON string + if (card.colors && typeof card.colors === 'string') { + try { + card.colors = JSON.parse(card.colors); + } catch (e) { + card.colors = []; + } + } + return card; + }); + res.status(200).json({ success: true, - cards: result.rows, + cards, pagination: { - page: 1, - limit: parseInt(limit), - total: result.rows.length, - pages: 1 + page: pageNum, + limit: limitNum, + total, + pages: totalPages, + hasMore: pageNum < totalPages }, filters: { - games: [...new Set(result.rows.map(card => card.game).filter(Boolean))], - rarities: [...new Set(result.rows.map(card => card.rarity).filter(Boolean))], - sets: [...new Set(result.rows.map(card => card.set_name).filter(Boolean))] + games: filterData.games || [], + rarities: filterData.rarities || [], + sets: filterData.sets || [] } }); } catch (error) { console.error('Error searching cards:', error); - res.status(500).json({ error: 'Internal server error' }); + res.status(500).json({ + success: false, + error: 'Internal server error', + message: error.message + }); } }