From b6f76297bc0a78e2f37f11a782aa1451e5ec0123 Mon Sep 17 00:00:00 2001 From: Randall Stillwell Date: Thu, 24 Jul 2025 10:30:21 -0500 Subject: [PATCH] Fix import issues: Add retry logic and better error handling - Added retry logic with exponential backoff for Pokemon API calls - Created Lorcana import endpoint with placeholder data - Improved error handling for 504 timeouts and 404 not found errors - Added longer delays for Pokemon imports to avoid rate limiting - Enhanced logging for better debugging of import issues - Fixed response parsing to handle different API response formats --- ...port-results-2025-07-24T15-23-54-843Z.json | 1150 +++++++++++++++++ pages/api/cards/import-lorcana.js | 143 ++ pages/api/cards/import-pokemon.js | 66 +- scripts/bulk-import-all.js | 12 +- 4 files changed, 1359 insertions(+), 12 deletions(-) create mode 100644 bulk-import-results-2025-07-24T15-23-54-843Z.json create mode 100644 pages/api/cards/import-lorcana.js diff --git a/bulk-import-results-2025-07-24T15-23-54-843Z.json b/bulk-import-results-2025-07-24T15-23-54-843Z.json new file mode 100644 index 0000000..cc23436 --- /dev/null +++ b/bulk-import-results-2025-07-24T15-23-54-843Z.json @@ -0,0 +1,1150 @@ +{ + "mtg": { + "total": 103, + "successful": 102, + "failed": 1, + "sets": [ + { + "code": "neo", + "name": "Kamigawa: Neon Dynasty", + "success": true + }, + { + "code": "vow", + "name": "Innistrad: Crimson Vow", + "success": true + }, + { + "code": "mid", + "name": "Innistrad: Midnight Hunt", + "success": true + }, + { + "code": "afr", + "name": "Adventures in the Forgotten Realms", + "success": true + }, + { + "code": "stx", + "name": "Strixhaven: School of Mages", + "success": true + }, + { + "code": "khm", + "name": "Kaldheim", + "success": true + }, + { + "code": "znr", + "name": "Zendikar Rising", + "success": true + }, + { + "code": "iko", + "name": "Ikoria: Lair of Behemoths", + "success": true + }, + { + "code": "thb", + "name": "Theros Beyond Death", + "success": true + }, + { + "code": "eld", + "name": "Throne of Eldraine", + "success": true + }, + { + "code": "m20", + "name": "Core Set 2020", + "success": true + }, + { + "code": "war", + "name": "War of the Spark", + "success": true + }, + { + "code": "rna", + "name": "Ravnica Allegiance", + "success": true + }, + { + "code": "grn", + "name": "Guilds of Ravnica", + "success": true + }, + { + "code": "m19", + "name": "Core Set 2019", + "success": true + }, + { + "code": "dom", + "name": "Dominaria", + "success": true + }, + { + "code": "rix", + "name": "Rivals of Ixalan", + "success": true + }, + { + "code": "xln", + "name": "Ixalan", + "success": true + }, + { + "code": "hou", + "name": "Hour of Devastation", + "success": true + }, + { + "code": "akh", + "name": "Amonkhet", + "success": true + }, + { + "code": "aer", + "name": "Aether Revolt", + "success": true + }, + { + "code": "kld", + "name": "Kaladesh", + "success": true + }, + { + "code": "emn", + "name": "Eldritch Moon", + "success": true + }, + { + "code": "soi", + "name": "Shadows over Innistrad", + "success": true + }, + { + "code": "ogw", + "name": "Oath of the Gatewatch", + "success": true + }, + { + "code": "bfz", + "name": "Battle for Zendikar", + "success": true + }, + { + "code": "ori", + "name": "Magic Origins", + "success": true + }, + { + "code": "dtk", + "name": "Dragons of Tarkir", + "success": true + }, + { + "code": "frf", + "name": "Fate Reforged", + "success": true + }, + { + "code": "ktk", + "name": "Khans of Tarkir", + "success": true + }, + { + "code": "m15", + "name": "Magic 2015", + "success": true + }, + { + "code": "jou", + "name": "Journey into Nyx", + "success": true + }, + { + "code": "bng", + "name": "Born of the Gods", + "success": true + }, + { + "code": "ths", + "name": "Theros", + "success": true + }, + { + "code": "m14", + "name": "Magic 2014", + "success": true + }, + { + "code": "dgm", + "name": "Dragon's Maze", + "success": true + }, + { + "code": "gtc", + "name": "Gatecrash", + "success": true + }, + { + "code": "rtr", + "name": "Return to Ravnica", + "success": true + }, + { + "code": "m13", + "name": "Magic 2013", + "success": true + }, + { + "code": "avr", + "name": "Avacyn Restored", + "success": true + }, + { + "code": "dka", + "name": "Dark Ascension", + "success": true + }, + { + "code": "isd", + "name": "Innistrad", + "success": true + }, + { + "code": "m12", + "name": "Magic 2012", + "success": true + }, + { + "code": "nph", + "name": "New Phyrexia", + "success": true + }, + { + "code": "mbs", + "name": "Mirrodin Besieged", + "success": true + }, + { + "code": "som", + "name": "Scars of Mirrodin", + "success": true + }, + { + "code": "m11", + "name": "Magic 2011", + "success": true + }, + { + "code": "roe", + "name": "Rise of the Eldrazi", + "success": true + }, + { + "code": "wwk", + "name": "Worldwake", + "success": true + }, + { + "code": "zen", + "name": "Zendikar", + "success": true + }, + { + "code": "m10", + "name": "Magic 2010", + "success": true + }, + { + "code": "arb", + "name": "Alara Reborn", + "success": true + }, + { + "code": "con", + "name": "Conflux", + "success": true + }, + { + "code": "ala", + "name": "Shards of Alara", + "success": true + }, + { + "code": "eve", + "name": "Eventide", + "success": true + }, + { + "code": "shm", + "name": "Shadowmoor", + "success": true + }, + { + "code": "mor", + "name": "Morningtide", + "success": true + }, + { + "code": "lrw", + "name": "Lorwyn", + "success": true + }, + { + "code": "fut", + "name": "Future Sight", + "success": true + }, + { + "code": "plc", + "name": "Planar Chaos", + "success": true + }, + { + "code": "tsp", + "name": "Time Spiral", + "success": true + }, + { + "code": "csp", + "name": "Coldsnap", + "success": true + }, + { + "code": "dis", + "name": "Dissension", + "success": true + }, + { + "code": "gpt", + "name": "Guildpact", + "success": true + }, + { + "code": "rav", + "name": "Ravnica: City of Guilds", + "success": true + }, + { + "code": "9ed", + "name": "Ninth Edition", + "success": true + }, + { + "code": "sok", + "name": "Saviors of Kamigawa", + "success": true + }, + { + "code": "bet", + "name": "Betrayers of Kamigawa", + "success": false, + "error": "Import failed" + }, + { + "code": "chk", + "name": "Champions of Kamigawa", + "success": true + }, + { + "code": "5dn", + "name": "Fifth Dawn", + "success": true + }, + { + "code": "dst", + "name": "Darksteel", + "success": true + }, + { + "code": "mrd", + "name": "Mirrodin", + "success": true + }, + { + "code": "8ed", + "name": "Eighth Edition", + "success": true + }, + { + "code": "scg", + "name": "Scourge", + "success": true + }, + { + "code": "lgn", + "name": "Legions", + "success": true + }, + { + "code": "ons", + "name": "Onslaught", + "success": true + }, + { + "code": "7ed", + "name": "Seventh Edition", + "success": true + }, + { + "code": "apc", + "name": "Apocalypse", + "success": true + }, + { + "code": "pls", + "name": "Planeshift", + "success": true + }, + { + "code": "inv", + "name": "Invasion", + "success": true + }, + { + "code": "6ed", + "name": "Classic Sixth Edition", + "success": true + }, + { + "code": "pcy", + "name": "Prophecy", + "success": true + }, + { + "code": "nem", + "name": "Nemesis", + "success": true + }, + { + "code": "mmq", + "name": "Mercadian Masques", + "success": true + }, + { + "code": "uds", + "name": "Urza's Destiny", + "success": true + }, + { + "code": "ulg", + "name": "Urza's Legacy", + "success": true + }, + { + "code": "usg", + "name": "Urza's Saga", + "success": true + }, + { + "code": "5ed", + "name": "Fifth Edition", + "success": true + }, + { + "code": "exo", + "name": "Exodus", + "success": true + }, + { + "code": "sth", + "name": "Stronghold", + "success": true + }, + { + "code": "tmp", + "name": "Tempest", + "success": true + }, + { + "code": "4ed", + "name": "Fourth Edition", + "success": true + }, + { + "code": "vis", + "name": "Visions", + "success": true + }, + { + "code": "all", + "name": "Alliances", + "success": true + }, + { + "code": "ice", + "name": "Ice Age", + "success": true + }, + { + "code": "3ed", + "name": "Revised Edition", + "success": true + }, + { + "code": "fem", + "name": "Fallen Empires", + "success": true + }, + { + "code": "drk", + "name": "The Dark", + "success": true + }, + { + "code": "leg", + "name": "Legends", + "success": true + }, + { + "code": "atq", + "name": "Antiquities", + "success": true + }, + { + "code": "arn", + "name": "Arabian Nights", + "success": true + }, + { + "code": "leb", + "name": "Limited Edition Beta", + "success": true + }, + { + "code": "lea", + "name": "Limited Edition Alpha", + "success": true + } + ] + }, + "pokemon": { + "total": 107, + "successful": 49, + "failed": 58, + "sets": [ + { + "code": "base1", + "name": "Base Set", + "success": false, + "error": "Import failed" + }, + { + "code": "base2", + "name": "Base Set 2", + "success": true + }, + { + "code": "base3", + "name": "Base Set 3", + "success": true + }, + { + "code": "gym1", + "name": "Gym Heroes", + "success": false, + "error": "Import failed" + }, + { + "code": "gym2", + "name": "Gym Challenge", + "success": false, + "error": "Import failed" + }, + { + "code": "neo1", + "name": "Neo Genesis", + "success": false, + "error": "Import failed" + }, + { + "code": "neo2", + "name": "Neo Discovery", + "success": false, + "error": "Import failed" + }, + { + "code": "neo3", + "name": "Neo Revelation", + "success": false, + "error": "Import failed" + }, + { + "code": "neo4", + "name": "Neo Destiny", + "success": false, + "error": "Import failed" + }, + { + "code": "ecard1", + "name": "Expedition Base Set", + "success": false, + "error": "Import failed" + }, + { + "code": "ecard2", + "name": "Aquapolis", + "success": false, + "error": "Import failed" + }, + { + "code": "ecard3", + "name": "Skyridge", + "success": false, + "error": "Import failed" + }, + { + "code": "ex1", + "name": "Ruby & Sapphire", + "success": false, + "error": "Import failed" + }, + { + "code": "ex2", + "name": "Sandstorm", + "success": false, + "error": "Import failed" + }, + { + "code": "ex3", + "name": "Dragon", + "success": true + }, + { + "code": "ex4", + "name": "Team Magma vs Team Aqua", + "success": true + }, + { + "code": "ex5", + "name": "Hidden Legends", + "success": true + }, + { + "code": "ex6", + "name": "FireRed & LeafGreen", + "success": false, + "error": "Import failed" + }, + { + "code": "ex7", + "name": "Team Rocket Returns", + "success": true + }, + { + "code": "ex8", + "name": "Deoxys", + "success": true + }, + { + "code": "ex9", + "name": "Emerald", + "success": true + }, + { + "code": "ex10", + "name": "Unseen Forces", + "success": true + }, + { + "code": "ex11", + "name": "Delta Species", + "success": true + }, + { + "code": "ex12", + "name": "Legend Maker", + "success": true + }, + { + "code": "ex13", + "name": "Holon Phantoms", + "success": true + }, + { + "code": "ex14", + "name": "Crystal Guardians", + "success": true + }, + { + "code": "ex15", + "name": "Dragon Frontiers", + "success": false, + "error": "Import failed" + }, + { + "code": "ex16", + "name": "Power Keepers", + "success": true + }, + { + "code": "dpp1", + "name": "Diamond & Pearl", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp2", + "name": "Mysterious Treasures", + "success": true + }, + { + "code": "dpp3", + "name": "Secret Wonders", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp4", + "name": "Great Encounters", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp5", + "name": "Majestic Dawn", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp6", + "name": "Legends Awakened", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp7", + "name": "Stormfront", + "success": false, + "error": "Import failed" + }, + { + "code": "dpp8", + "name": "Platinum", + "success": true + }, + { + "code": "dpp9", + "name": "Rising Rivals", + "success": true + }, + { + "code": "dpp10", + "name": "Supreme Victors", + "success": true + }, + { + "code": "dpp11", + "name": "Arceus", + "success": false, + "error": "Import failed" + }, + { + "code": "hgss1", + "name": "HeartGold & SoulSilver", + "success": false, + "error": "Import failed" + }, + { + "code": "hgss2", + "name": "Unleashed", + "success": false, + "error": "Import failed" + }, + { + "code": "hgss3", + "name": "Undaunted", + "success": false, + "error": "Import failed" + }, + { + "code": "hgss4", + "name": "Triumphant", + "success": true + }, + { + "code": "col1", + "name": "Call of Legends", + "success": true + }, + { + "code": "bw1", + "name": "Black & White", + "success": true + }, + { + "code": "bw2", + "name": "Emerging Powers", + "success": false, + "error": "Import failed" + }, + { + "code": "bw3", + "name": "Noble Victories", + "success": true + }, + { + "code": "bw4", + "name": "Next Destinies", + "success": true + }, + { + "code": "bw5", + "name": "Dark Explorers", + "success": false, + "error": "Import failed" + }, + { + "code": "bw6", + "name": "Dragons Exalted", + "success": false, + "error": "Import failed" + }, + { + "code": "bw7", + "name": "Boundaries Crossed", + "success": false, + "error": "Import failed" + }, + { + "code": "bw8", + "name": "Plasma Storm", + "success": false, + "error": "Import failed" + }, + { + "code": "bw9", + "name": "Plasma Freeze", + "success": false, + "error": "Import failed" + }, + { + "code": "bw10", + "name": "Plasma Blast", + "success": false, + "error": "Import failed" + }, + { + "code": "bw11", + "name": "Legendary Treasures", + "success": true + }, + { + "code": "xy1", + "name": "XY", + "success": true + }, + { + "code": "xy2", + "name": "Flashfire", + "success": true + }, + { + "code": "xy3", + "name": "Furious Fists", + "success": true + }, + { + "code": "xy4", + "name": "Phantom Forces", + "success": true + }, + { + "code": "xy5", + "name": "Primal Clash", + "success": false, + "error": "Import failed" + }, + { + "code": "xy6", + "name": "Double Crisis", + "success": false, + "error": "Import failed" + }, + { + "code": "xy7", + "name": "Roaring Skies", + "success": true + }, + { + "code": "xy8", + "name": "Ancient Origins", + "success": false, + "error": "Import failed" + }, + { + "code": "xy9", + "name": "BREAKthrough", + "success": true + }, + { + "code": "xy10", + "name": "BREAKpoint", + "success": true + }, + { + "code": "xy11", + "name": "Fates Collide", + "success": true + }, + { + "code": "xy12", + "name": "Steam Siege", + "success": false, + "error": "Import failed" + }, + { + "code": "xy12", + "name": "Evolutions", + "success": true + }, + { + "code": "sm1", + "name": "Sun & Moon", + "success": false, + "error": "Import failed" + }, + { + "code": "sm2", + "name": "Guardians Rising", + "success": false, + "error": "Import failed" + }, + { + "code": "sm3", + "name": "Burning Shadows", + "success": true + }, + { + "code": "sm4", + "name": "Crimson Invasion", + "success": true + }, + { + "code": "sm5", + "name": "Ultra Prism", + "success": false, + "error": "Import failed" + }, + { + "code": "sm6", + "name": "Forbidden Light", + "success": false, + "error": "Import failed" + }, + { + "code": "sm7", + "name": "Celestial Storm", + "success": true + }, + { + "code": "sm8", + "name": "Dragon Majesty", + "success": true + }, + { + "code": "sm9", + "name": "Lost Thunder", + "success": true + }, + { + "code": "sm10", + "name": "Team Up", + "success": true + }, + { + "code": "sm11", + "name": "Detective Pikachu", + "success": false, + "error": "Import failed" + }, + { + "code": "sm12", + "name": "Unbroken Bonds", + "success": false, + "error": "Import failed" + }, + { + "code": "sm115", + "name": "Unified Minds", + "success": false, + "error": "Import failed" + }, + { + "code": "sm116", + "name": "Hidden Fates", + "success": false, + "error": "Import failed" + }, + { + "code": "sm117", + "name": "Cosmic Eclipse", + "success": true + }, + { + "code": "swsh1", + "name": "Sword & Shield", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh2", + "name": "Rebel Clash", + "success": true + }, + { + "code": "swsh3", + "name": "Darkness Ablaze", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh4", + "name": "Champions Path", + "success": true + }, + { + "code": "swsh5", + "name": "Vivid Voltage", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh6", + "name": "Shining Fates", + "success": true + }, + { + "code": "swsh7", + "name": "Battle Styles", + "success": true + }, + { + "code": "swsh8", + "name": "Chilling Reign", + "success": true + }, + { + "code": "swsh9", + "name": "Evolving Skies", + "success": true + }, + { + "code": "swsh10", + "name": "Celebrations", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh11", + "name": "Fusion Strike", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh12", + "name": "Brilliant Stars", + "success": true + }, + { + "code": "swsh12", + "name": "Astral Radiance", + "success": true + }, + { + "code": "swsh12", + "name": "Pokemon GO", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh12", + "name": "Lost Origin", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh12", + "name": "Silver Tempest", + "success": false, + "error": "Import failed" + }, + { + "code": "swsh12", + "name": "Crown Zenith", + "success": false, + "error": "Import failed" + }, + { + "code": "sv1", + "name": "Scarlet & Violet", + "success": false, + "error": "Import failed" + }, + { + "code": "sv2", + "name": "Paldea Evolved", + "success": false, + "error": "Import failed" + }, + { + "code": "sv3", + "name": "Obsidian Flames", + "success": false, + "error": "Import failed" + }, + { + "code": "sv4", + "name": "151", + "success": false, + "error": "Import failed" + }, + { + "code": "sv5", + "name": "Paradox Rift", + "success": false, + "error": "Import failed" + }, + { + "code": "sv6", + "name": "Paldean Fates", + "success": false, + "error": "Import failed" + }, + { + "code": "sv7", + "name": "Temporal Forces", + "success": true + } + ] + }, + "lorcana": { + "total": 3, + "successful": 0, + "failed": 3, + "sets": [ + { + "code": "tfc", + "name": "The First Chapter", + "success": false, + "error": "Method not allowed" + }, + { + "code": "rotf", + "name": "Rise of the Floodborn", + "success": false, + "error": "Method not allowed" + }, + { + "code": "ink", + "name": "Into the Inklands", + "success": false, + "error": "Method not allowed" + } + ] + } +} \ No newline at end of file diff --git a/pages/api/cards/import-lorcana.js b/pages/api/cards/import-lorcana.js new file mode 100644 index 0000000..8960310 --- /dev/null +++ b/pages/api/cards/import-lorcana.js @@ -0,0 +1,143 @@ +import { sql } from '@vercel/postgres'; + +// Helper function to delay execution +const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +// Helper function to fetch with retry logic +async function fetchWithRetry(url, maxRetries = 3, delayMs = 2000) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const response = await fetch(url, { + headers: { + 'User-Agent': 'TCG-Vault/1.0', + 'Accept': 'application/json' + }, + timeout: 30000 // 30 second timeout + }); + + if (response.ok) { + return response; + } + + // If it's a 404, don't retry + if (response.status === 404) { + throw new Error(`Set not found: ${response.status}`); + } + + // If it's a 504 or 503, wait longer before retry + if (response.status === 504 || response.status === 503) { + console.log(`Attempt ${attempt}: Got ${response.status}, waiting ${delayMs * attempt}ms before retry...`); + await delay(delayMs * attempt); + continue; + } + + throw new Error(`Lorcana API error: ${response.status}`); + } catch (error) { + if (attempt === maxRetries) { + throw error; + } + console.log(`Attempt ${attempt} failed:`, error.message); + await delay(delayMs * attempt); + } + } +} + +export default async function handler(req, res) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { setCode } = req.body; + + if (!setCode) { + return res.status(400).json({ error: 'Set code is required' }); + } + + console.log(`Starting import for Lorcana set: ${setCode}`); + + // For now, we'll use a placeholder approach since Lorcana doesn't have a public API + // You can replace this with actual Lorcana API calls when available + const mockCards = [ + { + id: `lorcana-${setCode}-001`, + name: 'Sample Lorcana Card', + set: { name: setCode, id: setCode }, + number: '001', + rarity: 'Common', + supertype: 'Character', + types: ['Character'], + flavorText: 'A sample Lorcana card', + images: { small: null, large: null } + } + ]; + + console.log(`Using mock data for set ${setCode} (${mockCards.length} cards)`); + + let importedCount = 0; + let skippedCount = 0; + + for (const card of mockCards) { + try { + // Check if card already exists + const existingCard = await sql` + SELECT id FROM cards WHERE scryfall_id = ${card.id} + `; + + if (existingCard.rows.length > 0) { + skippedCount++; + continue; + } + + // Determine rarity + let rarity = card.rarity || 'Common'; + if (rarity.includes('Enchanted')) { + rarity = 'Enchanted'; + } else if (rarity.includes('Legendary')) { + rarity = 'Legendary'; + } else if (rarity.includes('Rare')) { + rarity = 'Rare'; + } + + // Insert card into database + await sql` + INSERT INTO cards ( + 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 + ) VALUES ( + ${card.name}, ${card.set.name}, ${card.set.id}, ${card.number}, + ${rarity}, 'Lorcana', null, null, ${card.supertype || 'Character'}, + ${JSON.stringify(card.types || [])}, ${card.flavorText || null}, + null, null, + ${card.images?.small || null}, ${card.images?.large || null}, + null, null, ${card.id}, true + ) + `; + + importedCount++; + } catch (error) { + console.error(`Error importing card ${card.name}:`, error); + skippedCount++; + } + } + + console.log(`Import completed for set ${setCode}: ${importedCount} imported, ${skippedCount} skipped`); + + res.status(200).json({ + success: true, + message: `Import completed for set ${setCode}`, + imported: importedCount, + skipped: skippedCount, + total: mockCards.length + }); + + } catch (error) { + console.error('Card import error:', error); + res.status(500).json({ + error: 'Import failed', + details: error.message + }); + } +} \ No newline at end of file diff --git a/pages/api/cards/import-pokemon.js b/pages/api/cards/import-pokemon.js index a7c2053..cc14347 100644 --- a/pages/api/cards/import-pokemon.js +++ b/pages/api/cards/import-pokemon.js @@ -1,5 +1,47 @@ import { sql } from '@vercel/postgres'; +// Helper function to delay execution +const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); + +// Helper function to fetch with retry logic +async function fetchWithRetry(url, maxRetries = 3, delayMs = 2000) { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const response = await fetch(url, { + headers: { + 'User-Agent': 'TCG-Vault/1.0', + 'Accept': 'application/json' + }, + timeout: 30000 // 30 second timeout + }); + + if (response.ok) { + return response; + } + + // If it's a 404, don't retry + if (response.status === 404) { + throw new Error(`Set not found: ${response.status}`); + } + + // If it's a 504 or 503, wait longer before retry + if (response.status === 504 || response.status === 503) { + console.log(`Attempt ${attempt}: Got ${response.status}, waiting ${delayMs * attempt}ms before retry...`); + await delay(delayMs * attempt); + continue; + } + + throw new Error(`Pokemon TCG API error: ${response.status}`); + } catch (error) { + if (attempt === maxRetries) { + throw error; + } + console.log(`Attempt ${attempt} failed:`, error.message); + await delay(delayMs * attempt); + } + } +} + export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); @@ -12,16 +54,26 @@ export default async function handler(req, res) { return res.status(400).json({ error: 'Set code is required' }); } - // Fetch cards from Pokemon TCG API - const response = await fetch(`https://api.pokemontcg.io/v2/cards?q=set.id:${setCode}&pageSize=250`); - - if (!response.ok) { - throw new Error(`Pokemon TCG API error: ${response.status}`); - } + console.log(`Starting import for Pokemon set: ${setCode}`); + // Fetch cards from Pokemon TCG API with retry logic + const response = await fetchWithRetry(`https://api.pokemontcg.io/v2/cards?q=set.id:${setCode}&pageSize=250`); + const data = await response.json(); const cards = data.data || []; + if (cards.length === 0) { + return res.status(200).json({ + success: true, + message: `No cards found for set ${setCode}`, + imported: 0, + skipped: 0, + total: 0 + }); + } + + console.log(`Found ${cards.length} cards for set ${setCode}`); + let importedCount = 0; let skippedCount = 0; @@ -79,6 +131,8 @@ export default async function handler(req, res) { } } + console.log(`Import completed for set ${setCode}: ${importedCount} imported, ${skippedCount} skipped`); + res.status(200).json({ success: true, message: `Import completed for set ${setCode}`, diff --git a/scripts/bulk-import-all.js b/scripts/bulk-import-all.js index 855bb06..9b6bc6f 100644 --- a/scripts/bulk-import-all.js +++ b/scripts/bulk-import-all.js @@ -244,8 +244,8 @@ async function importSet(game, setCode, setName) { const result = await response.json(); if (result.success) { - console.log(`✅ Successfully imported ${result.importedCount} cards from ${setName}`); - return { success: true, importedCount: result.importedCount }; + console.log(`✅ Successfully imported ${result.imported || result.importedCount || 0} cards from ${setName}`); + return { success: true, importedCount: result.imported || result.importedCount || 0 }; } else { console.log(`❌ Failed to import ${setName}: ${result.error}`); return { success: false, error: result.error }; @@ -279,7 +279,7 @@ async function bulkImport() { results.mtg.failed++; } - // Add a small delay to avoid overwhelming the API + // Add a delay to avoid overwhelming the API await new Promise(resolve => setTimeout(resolve, 1000)); } @@ -297,8 +297,8 @@ async function bulkImport() { results.pokemon.failed++; } - // Add a small delay to avoid overwhelming the API - await new Promise(resolve => setTimeout(resolve, 1000)); + // Add a longer delay for Pokemon to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 3000)); } // Import Lorcana sets @@ -315,7 +315,7 @@ async function bulkImport() { results.lorcana.failed++; } - // Add a small delay to avoid overwhelming the API + // Add a delay to avoid overwhelming the API await new Promise(resolve => setTimeout(resolve, 1000)); }