import { sql } from '@vercel/postgres'; import { checkSearchRateLimit } from '../../../lib/rate-limit.js'; export default async function handler(req, res) { if (req.method !== 'GET') { return res.status(405).json({ error: 'Method not allowed' }); } const { allowed, reset } = await checkSearchRateLimit(req); if (!allowed) { res.setHeader('Retry-After', Math.ceil((reset - Date.now()) / 1000)); return res.status(429).json({ error: 'Too many attempts. Try again later.' }); } try { const { query = '', game = 'all', rarity = 'all', set = 'all', minPrice = '', maxPrice = '', page = '1', limit = '50' } = req.query; const pageNum = parseInt(page) || 1; const limitNum = parseInt(limit) || 50; const offset = (pageNum - 1) * limitNum; // Define the columns we want to select (only existing columns) // Note: removed flavor_text, hp, type, form, weakness, retreat_cost as they don't exist in the current schema // 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, set_code, card_number, rarity, game, mana_cost, cmc, card_type, colors, oracle_text, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity FROM cards 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, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity 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, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity 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, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity 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, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity 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, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity 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 { // 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, set_code, card_number, rarity, game, mana_cost, cmc, card_type, colors, oracle_text, power, toughness, image_url, stock_image_url, current_price, market_price, scryfall_id, verified, quantity FROM cards 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, pagination: { page: pageNum, limit: limitNum, total, pages: totalPages, hasMore: pageNum < totalPages }, filters: { games: filterData.games || [], rarities: filterData.rarities || [], sets: filterData.sets || [] } }); } catch (error) { console.error('Error searching cards:', error); res.status(500).json({ success: false, error: 'Internal server error', message: error.message }); } }