diff --git a/api/proxy/lorcana.js b/api/proxy/lorcana.js new file mode 100644 index 0000000..c8c31a7 --- /dev/null +++ b/api/proxy/lorcana.js @@ -0,0 +1,67 @@ +import { NextResponse } from 'next/server'; + +// Proxy for Lorcana APIs to handle CORS +export async function GET(request) { + try { + const { searchParams } = new URL(request.url); + const query = searchParams.get('q'); + const search = searchParams.get('search'); + const limit = searchParams.get('limit') || '20'; + const api = searchParams.get('api') || 'lorcana'; // 'lorcana' or 'lorcast' + + let url; + let headers = {}; + + if (api === 'lorcana') { + // Lorcana API + if (search) { + url = `https://lorcana-api.com/api/v1/cards?search=${encodeURIComponent(search)}`; + } else { + url = `https://lorcana-api.com/api/v1/cards?limit=${limit}`; + } + headers = { + 'Accept': 'application/json', + 'User-Agent': 'TCG-Vault/1.0' + }; + } else { + // Lorcast API + if (query) { + url = `https://api.lorcast.com/v0/cards?q=${encodeURIComponent(query)}`; + } else { + url = `https://api.lorcast.com/v0/cards`; + } + headers = { + 'Accept': 'application/json', + 'User-Agent': 'TCG-Vault/1.0' + }; + } + + console.log(`🔗 Proxying request to: ${url}`); + + const response = await fetch(url, { headers }); + + if (!response.ok) { + console.error(`❌ Proxy error: ${response.status} ${response.statusText}`); + return NextResponse.json( + { error: `External API error: ${response.status}` }, + { status: response.status } + ); + } + + const data = await response.json(); + console.log(`✅ Proxy success: ${url}`); + + return NextResponse.json({ + success: true, + data: data, + source: api + }); + + } catch (error) { + console.error('Proxy error:', error); + return NextResponse.json( + { error: 'Failed to fetch from external API' }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/api/user-cards.js b/api/user-cards.js index 2c85085..f30d465 100644 --- a/api/user-cards.js +++ b/api/user-cards.js @@ -158,6 +158,8 @@ export async function GET(request) { } })); + console.log(`✅ Found ${userCards.length} user cards for user ${user.id}`); + return NextResponse.json({ success: true, data: userCards, diff --git a/src/services/cardDataSources.ts b/src/services/cardDataSources.ts index 0f5c011..f206cb0 100644 --- a/src/services/cardDataSources.ts +++ b/src/services/cardDataSources.ts @@ -6,6 +6,7 @@ const POKEMON_API_BASE_URL = 'https://api.pokemontcg.io/v2'; const YUGIOH_API_BASE_URL = 'https://db.ygoprodeck.com/api/v7'; const LORCAST_API_BASE_URL = 'https://api.lorcast.com/v0'; const LORCANA_API_BASE_URL = 'https://lorcana-api.com/api/v1'; +const PROXY_API_BASE_URL = 'https://tcg-vault.vercel.app/api/proxy/lorcana'; // Rate limiting utilities @@ -287,7 +288,48 @@ export const lorcanaService = { await lorcastLimiter.waitForSlot(); try { - // Try Lorcana API first (more reliable) + // Try proxy API first (handles CORS) + try { + const response = await fetch(`${PROXY_API_BASE_URL}?search=${encodeURIComponent(query)}&api=lorcana`); + + if (response.ok) { + const proxyData = await response.json(); + if (proxyData.success && proxyData.data) { + const cards = proxyData.data.cards || proxyData.data.data || proxyData.data || []; + + if (cards.length > 0) { + console.log(`✅ Found ${cards.length} Lorcana cards via proxy (${proxyData.source})`); + return cards.map((card: any) => ({ + id: card.id || card.uuid || `lorcana-${Math.random()}`, + name: card.name, + set_name: card.set?.name || card.set_name || '', + set_code: card.set?.code || card.set_code || '', + card_number: card.number || card.card_number || '', + rarity: card.rarity, + game: 'LORCANA', + mana_cost: card.cost?.toString() || card.mana_cost, + cmc: card.cost || card.cmc, + card_type: card.type || card.card_type, + colors: card.colors || [], + oracle_text: card.text || card.oracle_text || '', + power: card.strength?.toString() || card.power, + toughness: card.willpower?.toString() || card.toughness, + image_url: card.image_url || card.images?.small || card.images?.png, + stock_image_url: card.image_url || card.images?.small || card.images?.png, + current_price: card.price?.market || card.current_price || undefined, + market_price: card.price?.low || card.market_price || undefined, + verified: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + })); + } + } + } + } catch (e) { + console.log('❌ Proxy API failed, trying direct APIs'); + } + + // Fallback to direct APIs try { const response = await fetch(`${LORCANA_API_BASE_URL}/cards?search=${encodeURIComponent(query)}`); @@ -296,7 +338,7 @@ export const lorcanaService = { const cards = data.cards || data.data || data || []; if (cards.length > 0) { - console.log(`✅ Found ${cards.length} Lorcana cards from Lorcana API`); + console.log(`✅ Found ${cards.length} Lorcana cards from direct Lorcana API`); return cards.map((card: any) => ({ id: card.id || card.uuid || `lorcana-${Math.random()}`, name: card.name, @@ -323,18 +365,14 @@ export const lorcanaService = { } } } catch (e) { - console.log('❌ Lorcana API failed, trying Lorcast API'); + console.log('❌ Direct Lorcana API failed'); } - // Fallback to Lorcast API with multiple endpoints + // Final fallback to Lorcast API const searchEndpoints = [ `/cards?q=${encodeURIComponent(query)}`, `/cards?search=${encodeURIComponent(query)}`, - `/cards?query=${encodeURIComponent(query)}`, - `/api/cards?q=${encodeURIComponent(query)}`, - `/api/cards?search=${encodeURIComponent(query)}`, - `/v0/cards?q=${encodeURIComponent(query)}`, - `/v0/cards?search=${encodeURIComponent(query)}` + `/cards?query=${encodeURIComponent(query)}` ]; for (const endpoint of searchEndpoints) { @@ -390,7 +428,49 @@ export const lorcanaService = { await lorcastLimiter.waitForSlot(); try { - // Try Lorcana API first + // Try proxy API first (handles CORS) + try { + const response = await fetch(`${PROXY_API_BASE_URL}?search=${encodeURIComponent(name)}&api=lorcana`); + + if (response.ok) { + const proxyData = await response.json(); + if (proxyData.success && proxyData.data) { + const cards = proxyData.data.cards || proxyData.data.data || proxyData.data || []; + + if (cards.length > 0) { + const card = cards[0]; + console.log(`✅ Found Lorcana card "${name}" via proxy (${proxyData.source})`); + return { + id: card.id || card.uuid || `lorcana-${Math.random()}`, + name: card.name, + set_name: card.set?.name || card.set_name || '', + set_code: card.set?.code || card.set_code || '', + card_number: card.number || card.card_number || '', + rarity: card.rarity, + game: 'LORCANA', + mana_cost: card.cost?.toString() || card.mana_cost, + cmc: card.cost || card.cmc, + card_type: card.type || card.card_type, + colors: card.colors || [], + oracle_text: card.text || card.oracle_text || '', + power: card.strength?.toString() || card.power, + toughness: card.willpower?.toString() || card.toughness, + image_url: card.image_url || card.images?.small || card.images?.png, + stock_image_url: card.image_url || card.images?.small || card.images?.png, + current_price: card.price?.market || card.current_price || undefined, + market_price: card.price?.low || card.market_price || undefined, + verified: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + } + } + } + } catch (e) { + console.log('❌ Proxy API failed for card lookup, trying direct APIs'); + } + + // Fallback to direct Lorcana API try { const response = await fetch(`${LORCANA_API_BASE_URL}/cards?search=${encodeURIComponent(name)}`); @@ -400,7 +480,7 @@ export const lorcanaService = { if (cards.length > 0) { const card = cards[0]; - console.log(`✅ Found Lorcana card "${name}" from Lorcana API`); + console.log(`✅ Found Lorcana card "${name}" from direct Lorcana API`); return { id: card.id || card.uuid || `lorcana-${Math.random()}`, name: card.name, @@ -427,18 +507,14 @@ export const lorcanaService = { } } } catch (e) { - console.log('❌ Lorcana API failed for card lookup, trying Lorcast API'); + console.log('❌ Direct Lorcana API failed for card lookup'); } - // Fallback to Lorcast API + // Final fallback to Lorcast API const searchEndpoints = [ `/cards?q=name:${encodeURIComponent(name)}`, `/cards?search=${encodeURIComponent(name)}`, - `/cards?query=${encodeURIComponent(name)}`, - `/api/cards?q=name:${encodeURIComponent(name)}`, - `/api/cards?search=${encodeURIComponent(name)}`, - `/v0/cards?q=name:${encodeURIComponent(name)}`, - `/v0/cards?search=${encodeURIComponent(name)}` + `/cards?query=${encodeURIComponent(name)}` ]; for (const endpoint of searchEndpoints) { @@ -495,7 +571,53 @@ export const lorcanaService = { await lorcastLimiter.waitForSlot(); try { - // Try Lorcana API first + // Try proxy API first (handles CORS) + try { + const response = await fetch(`${PROXY_API_BASE_URL}?limit=${count}&api=lorcana`); + + if (response.ok) { + const proxyData = await response.json(); + if (proxyData.success && proxyData.data) { + const allCards = proxyData.data.cards || proxyData.data.data || proxyData.data || []; + + if (allCards.length > 0) { + console.log(`✅ Found ${allCards.length} cards via proxy (${proxyData.source})`); + + // Randomly select cards + const shuffled = allCards.sort(() => 0.5 - Math.random()); + const selectedCards = shuffled.slice(0, count); + + return selectedCards.map((card: any) => ({ + id: card.id || card.uuid || `lorcana-${Math.random()}`, + name: card.name, + set_name: card.set?.name || card.set_name || '', + set_code: card.set?.code || card.set_code || '', + card_number: card.number || card.card_number || '', + rarity: card.rarity, + game: 'LORCANA', + mana_cost: card.cost?.toString() || card.mana_cost, + cmc: card.cost || card.cmc, + card_type: card.type || card.card_type, + colors: card.colors || [], + oracle_text: card.text || card.oracle_text || '', + power: card.strength?.toString() || card.power, + toughness: card.willpower?.toString() || card.toughness, + image_url: card.image_url || card.images?.small || card.images?.png, + stock_image_url: card.image_url || card.images?.small || card.images?.png, + current_price: card.price?.market || card.current_price || undefined, + market_price: card.price?.low || card.market_price || undefined, + verified: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + })); + } + } + } + } catch (e) { + console.log('❌ Proxy API failed for random cards, trying direct APIs'); + } + + // Fallback to direct Lorcana API try { const response = await fetch(`${LORCANA_API_BASE_URL}/cards?limit=${count}`); @@ -504,7 +626,7 @@ export const lorcanaService = { const allCards = data.cards || data.data || data || []; if (allCards.length > 0) { - console.log(`✅ Found ${allCards.length} cards from Lorcana API`); + console.log(`✅ Found ${allCards.length} cards from direct Lorcana API`); // Randomly select cards const shuffled = allCards.sort(() => 0.5 - Math.random()); @@ -536,15 +658,14 @@ export const lorcanaService = { } } } catch (e) { - console.log('❌ Lorcana API failed for random cards, trying Lorcast API'); + console.log('❌ Direct Lorcana API failed for random cards'); } - // Fallback to Lorcast API + // Final fallback to Lorcast API const endpoints = [ '/cards', '/api/cards', - '/v0/cards', - '/api/v0/cards' + '/v0/cards' ]; let allCards: any[] = [];