diff --git a/pages/api/admin/index.js b/pages/api/admin/index.js index f238816..47e6886 100644 --- a/pages/api/admin/index.js +++ b/pages/api/admin/index.js @@ -1,4 +1,3 @@ -import { NextResponse } from 'next/server'; import { sql } from '@vercel/postgres'; import { verifyToken, isAdmin } from '../auth-utils.js'; @@ -102,17 +101,12 @@ async function loadPokemonCards() { try { let loadedCount = 0; let page = 1; - const pageSize = 250; // Max allowed by API + const pageSize = 250; // API limit while (loadedCount < 1000) { // Limit to 1000 for now await waitForRateLimit('pokemon'); - const response = await fetch(`https://api.pokemontcg.io/v2/cards?page=${page}&pageSize=${pageSize}`, { - headers: { - 'X-Api-Key': process.env.POKEMON_API_KEY || '' - } - }); - + const response = await fetch(`https://api.pokemontcg.io/v2/cards?page=${page}&pageSize=${pageSize}`); const data = await response.json(); if (!data.data || data.data.length === 0) break; @@ -123,9 +117,9 @@ async function loadPokemonCards() { 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, tcg_player_id, verified + stock_image_url, current_price, market_price, scryfall_id, verified ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) - ON CONFLICT (tcg_player_id) DO NOTHING + ON CONFLICT (scryfall_id) DO NOTHING `, [ card.name, card.set.name, @@ -133,17 +127,17 @@ async function loadPokemonCards() { card.number, card.rarity, 'POKEMON', - card.convertedRetreatCost?.toString() || null, - card.convertedRetreatCost, - card.supertype + (card.subtypes ? ' - ' + card.subtypes.join(', ') : ''), + null, // No mana cost in PokΓ©mon + null, // No CMC in PokΓ©mon + card.supertype, JSON.stringify(card.types || []), - card.flavorText || card.rules?.join(' ') || '', - card.attacks?.[0]?.damage || null, - card.hp || null, + card.rules?.join(' ') || '', + card.nationalPokedexNumbers?.[0] || null, + null, // No toughness in PokΓ©mon card.images?.large, card.images?.small, - card.cardmarket?.prices?.averageSellPrice ? parseFloat(card.cardmarket.prices.averageSellPrice) : null, - card.cardmarket?.prices?.lowPrice ? parseFloat(card.cardmarket.prices.lowPrice) : null, + card.cardmarket?.prices?.averageSellPrice || null, + card.cardmarket?.prices?.lowPrice || null, card.id, true ]); @@ -166,110 +160,59 @@ async function loadPokemonCards() { } } -// Load Lorcana cards from multiple sources +// Load Lorcana cards from Lorcana API async function loadLorcanaCards() { - console.log('🏰 Loading Lorcana cards from multiple sources...'); + console.log('🏰 Loading Lorcana cards from Lorcana API...'); try { let loadedCount = 0; - // Try Lorcana API first - try { - await waitForRateLimit('lorcana'); - const response = await fetch('https://api.lorcana-api.com/cards/fetch?pagesize=1000'); - const data = await response.json(); - - if (data.cards && data.cards.length > 0) { - console.log(`πŸ“Š Found ${data.cards.length} cards from Lorcana API`); - - for (const card of data.cards) { - try { - await sql.query(` - 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, verified - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) - ON CONFLICT (name, set_code, card_number) DO NOTHING - `, [ - card.name || card.card_name || card.title || '', - card.set?.name || card.set_name || '', - card.set?.code || card.set_code || '', - card.number || card.card_number || card.card_num || '', - card.rarity || card.rarity_name || '', - 'LORCANA', - card.cost?.toString() || card.mana_cost || card.ink_cost?.toString() || '', - card.cost || card.cmc || card.ink_cost || 0, - card.type || card.card_type || card.type_name || '', - JSON.stringify(card.colors || card.ink || []), - card.text || card.oracle_text || card.description || card.effect || '', - card.strength?.toString() || card.power || card.attack?.toString() || '', - card.willpower?.toString() || card.toughness || card.defense?.toString() || '', - card.image_url || card.images?.small || card.images?.png || card.image || '', - card.image_url || card.images?.small || card.images?.png || card.image || '', - card.price?.market || card.current_price || null, - card.price?.low || card.market_price || null, - true - ]); - - loadedCount++; - } catch (error) { - console.error(`❌ Error loading Lorcana card ${card.name}:`, error); - } - } - } - } catch (error) { - console.log('❌ Lorcana API failed, trying Lorcast...'); + // Get all cards from Lorcana API + await waitForRateLimit('lorcana'); + const response = await fetch('https://lorcana-api.com/api/v1/cards'); + const data = await response.json(); + + if (!data.data || data.data.length === 0) { + console.log('❌ No Lorcana cards found'); + return 0; } - // Try Lorcast API as fallback - if (loadedCount === 0) { + console.log(`πŸ“Š Found ${data.data.length} Lorcana cards to load`); + + for (const card of data.data) { try { - await waitForRateLimit('lorcana'); - const response = await fetch('https://api.lorcast.com/v0/cards'); - const data = await response.json(); + await sql.query(` + 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 ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19) + ON CONFLICT (scryfall_id) DO NOTHING + `, [ + card.name, + card.set?.name || 'Unknown Set', + card.set?.id || 'UNK', + card.number || '0', + card.rarity || 'Common', + 'LORCANA', + null, // No mana cost in Lorcana + card.cost || null, + card.type || 'Character', + JSON.stringify(card.colors || []), + card.text || '', + card.strength || null, + card.willpower || null, + card.images?.full || card.images?.large, + card.images?.small, + null, // No price data available + null, // No price data available + card.id, + true + ]); - if (data.cards && data.cards.length > 0) { - console.log(`πŸ“Š Found ${data.cards.length} cards from Lorcast API`); - - for (const card of data.cards) { - try { - await sql.query(` - 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, verified - ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) - ON CONFLICT (name, set_code, card_number) DO NOTHING - `, [ - card.name || card.card_name || card.title || '', - card.set?.name || card.set_name || '', - card.set?.code || card.set_code || '', - card.number || card.card_number || card.card_num || '', - card.rarity || card.rarity_name || '', - 'LORCANA', - card.cost?.toString() || card.mana_cost || card.ink_cost?.toString() || '', - card.cost || card.cmc || card.ink_cost || 0, - card.type || card.card_type || card.type_name || '', - JSON.stringify(card.colors || card.ink || []), - card.text || card.oracle_text || card.description || card.effect || '', - card.strength?.toString() || card.power || card.attack?.toString() || '', - card.willpower?.toString() || card.toughness || card.defense?.toString() || '', - card.image_url || card.images?.small || card.images?.png || card.image || '', - card.image_url || card.images?.small || card.images?.png || card.image || '', - card.price?.market || card.current_price || null, - card.price?.low || card.market_price || null, - true - ]); - - loadedCount++; - } catch (error) { - console.error(`❌ Error loading Lorcana card ${card.name}:`, error); - } - } - } + loadedCount++; } catch (error) { - console.error('❌ Lorcast API also failed:', error); + console.error(`❌ Error loading Lorcana card ${card.name}:`, error); } } @@ -281,196 +224,186 @@ async function loadLorcanaCards() { } } -// GET /api/admin - Get admin data (card counts, user stats) -export async function GET(request) { +// GET /api/admin - Get admin data +export default async function handler(req, res) { + // Set CORS headers + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + // Handle preflight requests + if (req.method === 'OPTIONS') { + res.status(200).end(); + return; + } + + if (req.method !== 'GET' && req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + try { // Temporarily bypass auth for testing - // const token = request.headers.get('authorization')?.replace('Bearer ', ''); + // const token = req.headers.authorization?.replace('Bearer ', ''); // const user = await verifyToken(token); // if (!user) { - // return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); + // return res.status(401).json({ error: 'Unauthorized' }); // } // if (!isAdmin(user)) { - // return NextResponse.json({ error: 'Admin access required' }, { status: 403 }); + // return res.status(403).json({ error: 'Admin access required' }); // } - const { searchParams } = new URL(request.url); - const action = searchParams.get('action'); + const { action } = req.query; // Simple test endpoint if (action === 'test') { - return NextResponse.json({ + return res.status(200).json({ success: true, message: 'Admin API is working!', timestamp: new Date().toISOString() }); } - if (action === 'card-counts') { - try { - // First check if the cards table exists - const tableCheck = await sql.query(` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = 'cards' - ); + if (req.method === 'GET') { + if (action === 'card-counts') { + try { + // First check if the cards table exists + const tableCheck = await sql.query(` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' + AND table_name = 'cards' + ); + `); + + if (!tableCheck.rows[0].exists) { + return res.status(200).json({ + success: true, + counts: {}, + total: 0, + message: 'Cards table does not exist yet' + }); + } + + // Get card counts from database + const result = await sql.query(` + SELECT + game, + COUNT(*) as count + FROM cards + GROUP BY game + `); + + const counts = {}; + result.rows.forEach(row => { + counts[row.game] = parseInt(row.count); + }); + + return res.status(200).json({ + success: true, + counts, + total: Object.values(counts).reduce((sum, count) => sum + count, 0) + }); + } catch (dbError) { + console.error('❌ Database error:', dbError); + return res.status(500).json({ + success: false, + error: 'Database error', + details: dbError.message + }); + } + } + + if (action === 'user-stats') { + // Get user statistics + const result = await sql.query(` + SELECT + COUNT(*) as total_users, + COUNT(CASE WHEN created_at >= NOW() - INTERVAL '7 days' THEN 1 END) as new_users_7d, + COUNT(CASE WHEN created_at >= NOW() - INTERVAL '30 days' THEN 1 END) as new_users_30d + FROM user_preferences `); - if (!tableCheck.rows[0].exists) { - return NextResponse.json({ + return res.status(200).json({ + success: true, + stats: result.rows[0] + }); + } + + return res.status(400).json({ error: 'Invalid action' }); + } + + if (req.method === 'POST') { + if (action === 'load-cards') { + const { game } = req.body; + + console.log(`πŸš€ Starting card loading process for game: ${game}`); + + let results = {}; + + if (game === 'MTG' || game === 'ALL') { + results.mtg = await loadMTGCards(); + } + + if (game === 'POKEMON' || game === 'ALL') { + results.pokemon = await loadPokemonCards(); + } + + if (game === 'LORCANA' || game === 'ALL') { + results.lorcana = await loadLorcanaCards(); + } + + const totalLoaded = Object.values(results).reduce((sum, count) => sum + count, 0); + + console.log(`πŸŽ‰ Card loading completed! Total loaded: ${totalLoaded}`); + + return res.status(200).json({ + success: true, + message: `Successfully loaded ${totalLoaded} cards`, + results + }); + } + + if (action === 'manage-users') { + const { operation, userId, data } = req.body; + + if (operation === 'promote') { + await sql.query(` + UPDATE user_preferences + SET roles = array_append(roles, 'admin') + WHERE user_id = $1 + `, [userId]); + + return res.status(200).json({ success: true, - counts: {}, - total: 0, - message: 'Cards table does not exist yet' + message: 'User promoted to admin' }); } - // Get card counts from database - const result = await sql.query(` - SELECT - game, - COUNT(*) as count - FROM cards - GROUP BY game - `); + if (operation === 'demote') { + await sql.query(` + UPDATE user_preferences + SET roles = array_remove(roles, 'admin') + WHERE user_id = $1 + `, [userId]); + + return res.status(200).json({ + success: true, + message: 'User demoted from admin' + }); + } - const counts = {}; - result.rows.forEach(row => { - counts[row.game] = parseInt(row.count); - }); - - return NextResponse.json({ - success: true, - counts, - total: Object.values(counts).reduce((sum, count) => sum + count, 0) - }); - } catch (dbError) { - console.error('❌ Database error:', dbError); - return NextResponse.json({ - success: false, - error: 'Database error', - details: dbError.message - }, { status: 500 }); + return res.status(400).json({ error: 'Invalid operation' }); } - } - - if (action === 'user-stats') { - // Get user statistics - const result = await sql.query(` - SELECT - COUNT(*) as total_users, - COUNT(CASE WHEN created_at >= NOW() - INTERVAL '7 days' THEN 1 END) as new_users_7d, - COUNT(CASE WHEN created_at >= NOW() - INTERVAL '30 days' THEN 1 END) as new_users_30d - FROM user_preferences - `); - return NextResponse.json({ - success: true, - stats: result.rows[0] - }); + return res.status(400).json({ error: 'Invalid action' }); } - return NextResponse.json({ error: 'Invalid action' }, { status: 400 }); - } catch (error) { - console.error('❌ Error in admin GET:', error); - return NextResponse.json( - { error: 'Failed to get admin data', details: error.message }, - { status: 500 } - ); - } -} - -// POST /api/admin - Load cards or manage users -export async function POST(request) { - try { - // Temporarily bypass auth for testing - // const token = request.headers.get('authorization')?.replace('Bearer ', ''); - // const user = await verifyToken(token); - - // if (!user) { - // return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - // } - - // if (!isAdmin(user)) { - // return NextResponse.json({ error: 'Admin access required' }, { status: 403 }); - // } - - const { searchParams } = new URL(request.url); - const action = searchParams.get('action'); - - if (action === 'load-cards') { - const { game } = await request.json(); - - console.log(`πŸš€ Starting card loading process for game: ${game}`); - - let results = {}; - - if (game === 'MTG' || game === 'ALL') { - results.mtg = await loadMTGCards(); - } - - if (game === 'POKEMON' || game === 'ALL') { - results.pokemon = await loadPokemonCards(); - } - - if (game === 'LORCANA' || game === 'ALL') { - results.lorcana = await loadLorcanaCards(); - } - - const totalLoaded = Object.values(results).reduce((sum, count) => sum + count, 0); - - console.log(`πŸŽ‰ Card loading completed! Total loaded: ${totalLoaded}`); - - return NextResponse.json({ - success: true, - message: `Successfully loaded ${totalLoaded} cards`, - results - }); - } - - if (action === 'manage-users') { - const { operation, userId, data } = await request.json(); - - if (operation === 'promote') { - await sql.query(` - UPDATE user_preferences - SET roles = array_append(roles, 'admin') - WHERE user_id = $1 - `, [userId]); - - return NextResponse.json({ - success: true, - message: 'User promoted to admin' - }); - } - - if (operation === 'demote') { - await sql.query(` - UPDATE user_preferences - SET roles = array_remove(roles, 'admin') - WHERE user_id = $1 - `, [userId]); - - return NextResponse.json({ - success: true, - message: 'User demoted from admin' - }); - } - - return NextResponse.json({ error: 'Invalid operation' }, { status: 400 }); - } - - return NextResponse.json({ error: 'Invalid action' }, { status: 400 }); - - } catch (error) { - console.error('❌ Error in admin POST:', error); - return NextResponse.json( - { error: 'Failed to process admin action', details: error.message }, - { status: 500 } + console.error('❌ Error in admin API:', error); + return res.status(500).json( + { error: 'Failed to process admin request', details: error.message } ); } } \ No newline at end of file