diff --git a/api/admin/promote-user.js b/api/admin/promote-user.js deleted file mode 100644 index 3dc85c1..0000000 --- a/api/admin/promote-user.js +++ /dev/null @@ -1,130 +0,0 @@ -const { Pool } = require('pg'); - -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}); - -export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - try { - console.log('Request body type:', typeof req.body); - console.log('Request body:', req.body); - - const { username, userId } = req.body; - - if (!username && !userId) { - return res.status(400).json({ - error: 'Either username or userId is required' - }); - } - - const client = await pool.connect(); - - try { - // Find the user - let userQuery; - let userParams; - - if (userId) { - userQuery = 'SELECT id, username, email FROM users WHERE id = $1'; - userParams = [userId]; - } else { - userQuery = 'SELECT id, username, email FROM users WHERE username = $1'; - userParams = [username]; - } - - const userResult = await client.query(userQuery, userParams); - - if (userResult.rows.length === 0) { - return res.status(404).json({ - error: 'User not found' - }); - } - - const user = userResult.rows[0]; - - // Check if user is already an admin - const adminCheckQuery = ` - SELECT ur.user_id - FROM user_roles ur - JOIN roles r ON ur.role_id = r.id - WHERE ur.user_id = $1 AND r.name = 'admin' - `; - - const adminCheck = await client.query(adminCheckQuery, [user.id]); - - if (adminCheck.rows.length > 0) { - return res.status(200).json({ - success: true, - message: 'User is already an admin', - user: { - id: user.id, - username: user.username, - email: user.email - } - }); - } - - // Get admin role ID - const roleResult = await client.query( - 'SELECT id FROM roles WHERE name = $1', - ['admin'] - ); - - if (roleResult.rows.length === 0) { - return res.status(500).json({ - error: 'Admin role not found in database' - }); - } - - const adminRoleId = roleResult.rows[0].id; - - // Add user to admin role - await client.query( - 'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)', - [user.id, adminRoleId] - ); - - // Get updated user info with roles - const updatedUserQuery = ` - SELECT - u.id, u.username, u.email, u.first_name, u.last_name, - ARRAY_AGG(DISTINCT r.name) as roles - FROM users u - LEFT JOIN user_roles ur ON u.id = ur.user_id - LEFT JOIN roles r ON ur.role_id = r.id - WHERE u.id = $1 - GROUP BY u.id, u.username, u.email, u.first_name, u.last_name - `; - - const updatedUser = await client.query(updatedUserQuery, [user.id]); - - res.status(200).json({ - success: true, - message: 'User successfully promoted to admin', - user: { - id: updatedUser.rows[0].id, - username: updatedUser.rows[0].username, - email: updatedUser.rows[0].email, - firstName: updatedUser.rows[0].first_name, - lastName: updatedUser.rows[0].last_name, - roles: updatedUser.rows[0].roles || [] - } - }); - - } finally { - client.release(); - } - - } catch (error) { - console.error('Promote user error:', error); - res.status(500).json({ - error: 'Internal server error', - details: error.message - }); - } -} \ No newline at end of file diff --git a/api/admin/update-preferences-schema.js b/api/admin/update-preferences-schema.js deleted file mode 100644 index e897699..0000000 --- a/api/admin/update-preferences-schema.js +++ /dev/null @@ -1,65 +0,0 @@ -const { Pool } = require('pg'); - -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}); - -export default async function handler(req, res) { - if (req.method !== 'POST') { - return res.status(405).json({ error: 'Method not allowed' }); - } - - const client = await pool.connect(); - - try { - console.log('Updating user_preferences schema to add OCR settings...'); - - // Check if ocr_settings column already exists - const columnCheck = await client.query(` - SELECT EXISTS ( - SELECT FROM information_schema.columns - WHERE table_schema = 'public' - AND table_name = 'user_preferences' - AND column_name = 'ocr_settings' - ); - `); - - if (columnCheck.rows[0].exists) { - return res.status(200).json({ - success: true, - message: 'OCR settings column already exists', - already_updated: true - }); - } - - // Add ocr_settings column - await client.query(` - ALTER TABLE user_preferences - ADD COLUMN ocr_settings JSONB DEFAULT '{ - "preferred_service": "openai", - "openai_api_key": "", - "ollama_url": "http://localhost:11434", - "auto_add_to_collection": false, - "confidence_threshold": 80 - }'::jsonb; - `); - - console.log('✅ OCR settings column added to user_preferences table'); - - res.status(200).json({ - success: true, - message: 'User preferences schema updated successfully with OCR settings' - }); - - } catch (error) { - console.error('Schema update failed:', error); - res.status(500).json({ - success: false, - error: 'Schema update failed', - details: error.message - }); - } finally { - client.release(); - } -} \ No newline at end of file diff --git a/api/migrate.ts b/api/migrate.ts deleted file mode 100644 index dc6d655..0000000 --- a/api/migrate.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { Pool } from 'pg'; - -// Use require for JSON import to avoid module issues -const cardsData = require('../src/data/cards.json'); - -// Create PostgreSQL connection pool -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}); - -interface JsonCard { - id: number; - name: string; - set_name?: string; - set_code?: string; - card_number?: string; - rarity?: string; - game: string; - mana_cost?: string; - cmc?: number; - card_type?: string; - colors?: string[] | null; - oracle_text?: string; - flavor_text?: string | null; - power?: string | null; - toughness?: string | null; - loyalty?: string | null; - artist?: string | null; - image_url?: string; - stock_image_url?: string; - artwork_crop_coords?: any; - current_price?: number; - market_price?: number; - low_price?: number | null; - high_price?: number | null; - price_last_updated?: string | null; - ocr_confidence?: number | null; - ocr_raw_text?: string | null; - scryfall_id?: string | null; - tcg_player_id?: string | null; - verified: number; // JSON has number, but we want boolean - created_at?: string; - updated_at?: string; -} - -export default async function handler(req: NextRequest) { - // Only allow POST requests for security - if (req.method !== 'POST') { - return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), { - status: 405, - headers: { 'Content-Type': 'application/json' }, - }); - } - - const client = await pool.connect(); - - try { - console.log('Starting card migration to Neon database...'); - - // Check if cards already exist to prevent duplicate migration - const existingCards = await client.query('SELECT COUNT(*) as count FROM cards'); - const cardCount = existingCards.rows[0]?.count || 0; - - if (cardCount > 0) { - return new NextResponse(JSON.stringify({ - message: `Database already contains ${cardCount} cards. Migration skipped.`, - cards_count: cardCount - }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); - } - - // Migrate cards from JSON - const cards = cardsData as JsonCard[]; - console.log(`Migrating ${cards.length} cards...`); - - let migratedCount = 0; - const errors: string[] = []; - - for (const card of cards) { - try { - await client.query(` - INSERT INTO cards ( - name, set_name, set_code, card_number, rarity, game, mana_cost, cmc, - card_type, colors, oracle_text, flavor_text, power, toughness, loyalty, - artist, image_url, stock_image_url, artwork_crop_coords, current_price, - market_price, low_price, high_price, price_last_updated, ocr_confidence, - ocr_raw_text, scryfall_id, tcg_player_id, verified, created_at, updated_at - ) VALUES ( - $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, - $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31 - ) - `, [ - card.name, - card.set_name || null, - card.set_code || null, - card.card_number || null, - card.rarity || null, - card.game, - card.mana_cost || null, - card.cmc || null, - card.card_type || null, - card.colors ? JSON.stringify(card.colors) : null, - card.oracle_text || null, - card.flavor_text || null, - card.power || null, - card.toughness || null, - card.loyalty || null, - card.artist || null, - card.image_url || null, - card.stock_image_url || null, - card.artwork_crop_coords ? JSON.stringify(card.artwork_crop_coords) : null, - card.current_price || null, - card.market_price || null, - card.low_price || null, - card.high_price || null, - card.price_last_updated ? new Date(card.price_last_updated) : null, - card.ocr_confidence || null, - card.ocr_raw_text || null, - card.scryfall_id || null, - card.tcg_player_id || null, - Boolean(card.verified), - card.created_at ? new Date(card.created_at) : new Date(), - card.updated_at ? new Date(card.updated_at) : new Date() - ]); - migratedCount++; - } catch (error) { - const errorMsg = `Error migrating card ${card.name}: ${(error as Error).message}`; - console.error(errorMsg); - errors.push(errorMsg); - } - } - - // Get final count to verify - const finalCount = await client.query('SELECT COUNT(*) as count FROM cards'); - const finalCardCount = finalCount.rows[0]?.count || 0; - - console.log(`✅ Successfully migrated ${migratedCount} cards to Neon database!`); - - return new NextResponse(JSON.stringify({ - success: true, - message: `Successfully migrated ${migratedCount} out of ${cards.length} cards`, - cards_migrated: migratedCount, - total_cards_in_db: finalCardCount, - errors: errors.length > 0 ? errors : undefined - }), { - status: 200, - headers: { 'Content-Type': 'application/json' }, - }); - - } catch (error) { - console.error('Migration failed:', error); - return new NextResponse(JSON.stringify({ - success: false, - error: 'Migration failed', - details: (error as Error).message - }), { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }); - } finally { - client.release(); - } -} \ No newline at end of file diff --git a/api/simple.js b/api/simple.js deleted file mode 100644 index 9ff480e..0000000 --- a/api/simple.js +++ /dev/null @@ -1,12 +0,0 @@ -export default function handler(req, res) { - res.status(200).json({ - message: 'Simple JS endpoint works!', - method: req.method, - timestamp: new Date().toISOString(), - environment: process.env.NODE_ENV || 'unknown', - has_database_url: !!process.env.DATABASE_URL, - has_jwt_secret: !!process.env.JWT_SECRET, - database_url_preview: process.env.DATABASE_URL ? - process.env.DATABASE_URL.substring(0, 30) + '...' : 'not set' - }); -} \ No newline at end of file diff --git a/api/v1/cards/[id].ts b/api/v1/cards/[id].ts deleted file mode 100644 index 598d1b9..0000000 --- a/api/v1/cards/[id].ts +++ /dev/null @@ -1,122 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { Pool } from 'pg' - -// Create PostgreSQL connection pool -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}) - -interface Card { - id: number - name: string - set_name?: string - set_code?: string - card_number?: string - rarity?: string - game: string - mana_cost?: string - cmc?: number - card_type?: string - colors?: string[] - oracle_text?: string - flavor_text?: string - power?: string - toughness?: string - artist?: string - image_url?: string - stock_image_url?: string - artwork_crop_coords?: any - current_price?: number - market_price?: number - verified: boolean - created_at?: string - updated_at?: string -} - -export default async function handler(req: NextRequest) { - // Enable CORS - const headers = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - } - - if (req.method === 'OPTIONS') { - return new NextResponse(null, { status: 200, headers }) - } - - if (req.method !== 'GET') { - return new NextResponse(JSON.stringify({ error: 'Method not allowed' }), { - status: 405, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } - - try { - // Extract card ID from URL - const url = new URL(req.url) - const pathParts = url.pathname.split('/') - const cardId = pathParts[pathParts.length - 1] - - if (!cardId || isNaN(parseInt(cardId))) { - return new NextResponse(JSON.stringify({ error: 'Invalid card ID' }), { - status: 400, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } - - // Query the database for the specific card - const client = await pool.connect() - try { - const result = await client.query('SELECT * FROM cards WHERE id = $1', [parseInt(cardId)]) - - if (result.rows.length === 0) { - return new NextResponse(JSON.stringify({ error: 'Card not found' }), { - status: 404, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } - - // Transform the result to match expected format - const card = { - ...result.rows[0], - colors: result.rows[0].colors ? (typeof result.rows[0].colors === 'string' ? JSON.parse(result.rows[0].colors) : result.rows[0].colors) : null, - artwork_crop_coords: result.rows[0].artwork_crop_coords ? - (typeof result.rows[0].artwork_crop_coords === 'string' ? JSON.parse(result.rows[0].artwork_crop_coords) : result.rows[0].artwork_crop_coords) : null - } - - return new NextResponse(JSON.stringify(card), { - status: 200, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } finally { - client.release() - } - - } catch (error) { - console.error('API Error:', error) - return new NextResponse(JSON.stringify({ - error: 'Internal server error', - details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined - }), { - status: 500, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } -} diff --git a/api/v1/cards/index.ts b/api/v1/cards/index.ts deleted file mode 100644 index 14b431b..0000000 --- a/api/v1/cards/index.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { Pool } from 'pg' - -// Create PostgreSQL connection pool -const pool = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, -}) - -interface Card { - id: number - name: string - set_name?: string - set_code?: string - card_number?: string - rarity?: string - game: string - mana_cost?: string - cmc?: number - card_type?: string - colors?: string[] - oracle_text?: string - flavor_text?: string - power?: string - toughness?: string - artist?: string - image_url?: string - stock_image_url?: string - artwork_crop_coords?: any - current_price?: number - market_price?: number - verified: boolean - created_at?: string - updated_at?: string -} - -export default async function handler(req: NextRequest) { - // Enable CORS - const headers = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - } - - if (req.method === 'OPTIONS') { - return new NextResponse(null, { status: 200, headers }) - } - - try { - const url = new URL(req.url) - const searchParams = url.searchParams - - // Get query parameters - const skip = parseInt(searchParams.get('skip') || '0') - const limit = parseInt(searchParams.get('limit') || '100') - const search = searchParams.get('search') - const game = searchParams.get('game') - const set_name = searchParams.get('set_name') - - // Build the query dynamically - let baseQuery = 'SELECT * FROM cards WHERE 1=1' - let queryParams: any[] = [] - let paramIndex = 1 - - if (game) { - baseQuery += ` AND game = $${paramIndex}` - queryParams.push(game) - paramIndex++ - } - - if (set_name) { - baseQuery += ` AND set_name = $${paramIndex}` - queryParams.push(set_name) - paramIndex++ - } - - if (search) { - baseQuery += ` AND (name ILIKE $${paramIndex} OR oracle_text ILIKE $${paramIndex} OR card_type ILIKE $${paramIndex})` - queryParams.push(`%${search}%`) - paramIndex++ - } - - baseQuery += ' ORDER BY name' - - if (limit > 0) { - baseQuery += ` LIMIT $${paramIndex}` - queryParams.push(limit) - paramIndex++ - } - - if (skip > 0) { - baseQuery += ` OFFSET $${paramIndex}` - queryParams.push(skip) - } - - // Execute the query - const client = await pool.connect() - try { - const result = await client.query(baseQuery, queryParams) - - // Transform the result to match expected format - const cards = result.rows.map((row: any) => ({ - ...row, - colors: row.colors ? (typeof row.colors === 'string' ? JSON.parse(row.colors) : row.colors) : null, - artwork_crop_coords: row.artwork_crop_coords ? - (typeof row.artwork_crop_coords === 'string' ? JSON.parse(row.artwork_crop_coords) : row.artwork_crop_coords) : null - })) - - return new NextResponse(JSON.stringify(cards), { - status: 200, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } finally { - client.release() - } - - } catch (error) { - console.error('API Error:', error) - return new NextResponse(JSON.stringify({ - error: 'Internal server error', - details: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined - }), { - status: 500, - headers: { - ...headers, - 'Content-Type': 'application/json', - }, - }) - } -} diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index fb53037..1d98c7e 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -33,7 +33,7 @@ const Settings: React.FC = () => { useEffect(() => { loadPreferences(); - }, [user]); + }, [user]); // eslint-disable-line react-hooks/exhaustive-deps const loadPreferences = async () => { if (!user) return; diff --git a/src/services/aiOcr.ts b/src/services/aiOcr.ts index 0cb7c24..6c87a52 100644 --- a/src/services/aiOcr.ts +++ b/src/services/aiOcr.ts @@ -159,18 +159,18 @@ class AICardOCR { if (line.startsWith('```') || line.startsWith('#')) continue; // Look for key-value patterns - if (line.match(/card\s*name[:\-]?\s*(.+)/i)) { - cardName = line.replace(/card\s*name[:\-]?\s*/i, '').replace(/['"]/g, ''); - } else if (line.match(/name[:\-]?\s*(.+)/i) && !cardName) { - cardName = line.replace(/name[:\-]?\s*/i, '').replace(/['"]/g, ''); - } else if (line.match(/set[:\-]?\s*(.+)/i)) { - setName = line.replace(/set[:\-]?\s*/i, '').replace(/['"]/g, ''); - } else if (line.match(/hp[:\-]?\s*(\d+)/i)) { - hp = line.match(/hp[:\-]?\s*(\d+)/i)?.[1] || ''; - } else if (line.match(/type[:\-]?\s*(.+)/i)) { - cardType = line.replace(/type[:\-]?\s*/i, '').replace(/['"]/g, ''); - } else if (line.match(/rarity[:\-]?\s*(.+)/i)) { - rarity = line.replace(/rarity[:\-]?\s*/i, '').replace(/['"]/g, ''); + if (line.match(/card\s*name[:−]?\s*(.+)/i)) { + cardName = line.replace(/card\s*name[:−]?\s*/i, '').replace(/['"]/g, ''); + } else if (line.match(/name[:−]?\s*(.+)/i) && !cardName) { + cardName = line.replace(/name[:−]?\s*/i, '').replace(/['"]/g, ''); + } else if (line.match(/set[:−]?\s*(.+)/i)) { + setName = line.replace(/set[:−]?\s*/i, '').replace(/['"]/g, ''); + } else if (line.match(/hp[:−]?\s*(\d+)/i)) { + hp = line.match(/hp[:−]?\s*(\d+)/i)?.[1] || ''; + } else if (line.match(/type[:−]?\s*(.+)/i)) { + cardType = line.replace(/type[:−]?\s*/i, '').replace(/['"]/g, ''); + } else if (line.match(/rarity[:−]?\s*(.+)/i)) { + rarity = line.replace(/rarity[:−]?\s*/i, '').replace(/['"]/g, ''); } }