🔧 Gemini AI Integration: - Added Google Gemini API as default OCR service - Auto-configures from GEMINI_AI_API_KEY environment variable - Fixed Puter.js authentication issues - Enhanced OCR settings with connection testing 🎨 Redesigned Scanner Queue: - New thumbnail + content layout with checkbox overlay - Smart quantity management (duplicates increment quantity) - Complete card information display from database - Two-row action layout (primary/secondary actions) - Floating bottom toolbar for bulk actions - Real card images from database �� Enhanced User Experience: - Fixed Canvas2D performance warnings - Better error handling and fallbacks - Improved responsive design - Database confirmation indicators - Professional card scanning workflow 📱 Mobile Ready: - Optimized layouts for mobile scanning - Touch-friendly controls and interactions - Improved visual feedback and status indicators
164 lines
No EOL
4.6 KiB
JavaScript
164 lines
No EOL
4.6 KiB
JavaScript
import { sql } from '@vercel/postgres';
|
|
import { getUserFromRequest } from '../../../lib/permission-middleware';
|
|
|
|
export default async function handler(req, res) {
|
|
// Set CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, 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') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
try {
|
|
// Get authenticated user
|
|
const user = await getUserFromRequest(req);
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
const {
|
|
search = '',
|
|
tcg = 'all',
|
|
rarity = 'all',
|
|
set = 'all',
|
|
valueRange = 'all',
|
|
page = '1',
|
|
limit = '50'
|
|
} = req.query;
|
|
|
|
const pageNum = parseInt(page) || 1;
|
|
const limitNum = parseInt(limit) || 50;
|
|
const offset = (pageNum - 1) * limitNum;
|
|
|
|
// Build WHERE conditions
|
|
let whereConditions = ['uc.user_id = $1'];
|
|
let params = [user.userId];
|
|
let paramIndex = 2;
|
|
|
|
if (search.trim()) {
|
|
whereConditions.push(`c.name ILIKE $${paramIndex}`);
|
|
params.push(`%${search.trim()}%`);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (tcg !== 'all') {
|
|
whereConditions.push(`c.game = $${paramIndex}`);
|
|
params.push(tcg);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (rarity !== 'all') {
|
|
whereConditions.push(`c.rarity = $${paramIndex}`);
|
|
params.push(rarity);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (set !== 'all') {
|
|
whereConditions.push(`c.set_name = $${paramIndex}`);
|
|
params.push(set);
|
|
paramIndex++;
|
|
}
|
|
|
|
if (valueRange !== 'all') {
|
|
// Handle value range filtering
|
|
const ranges = {
|
|
'under-1': [0, 1],
|
|
'1-5': [1, 5],
|
|
'5-25': [5, 25],
|
|
'25-100': [25, 100],
|
|
'over-100': [100, 999999]
|
|
};
|
|
|
|
if (ranges[valueRange]) {
|
|
const [min, max] = ranges[valueRange];
|
|
if (valueRange === 'over-100') {
|
|
whereConditions.push(`c.market_price >= $${paramIndex}`);
|
|
params.push(min);
|
|
paramIndex++;
|
|
} else {
|
|
whereConditions.push(`c.market_price >= $${paramIndex} AND c.market_price <= $${paramIndex + 1}`);
|
|
params.push(min, max);
|
|
paramIndex += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
const whereClause = whereConditions.join(' AND ');
|
|
|
|
// Get owned cards with user ownership data
|
|
const cardsQuery = `
|
|
SELECT
|
|
c.id, c.name, c.set_name, c.set_code, c.card_number, c.rarity, c.game,
|
|
c.mana_cost, c.cmc, c.card_type, c.colors, c.oracle_text,
|
|
c.power, c.toughness, c.image_url, c.stock_image_url,
|
|
c.current_price, c.market_price, c.scryfall_id, c.verified,
|
|
uc.quantity as owned_quantity,
|
|
uc.condition,
|
|
uc.created_at as owned_since
|
|
FROM cards c
|
|
INNER JOIN user_cards uc ON c.id = uc.card_id
|
|
WHERE ${whereClause}
|
|
ORDER BY c.name ASC
|
|
LIMIT $${paramIndex} OFFSET $${paramIndex + 1}
|
|
`;
|
|
|
|
const countQuery = `
|
|
SELECT COUNT(*) as total
|
|
FROM cards c
|
|
INNER JOIN user_cards uc ON c.id = uc.card_id
|
|
WHERE ${whereClause}
|
|
`;
|
|
|
|
// Execute queries
|
|
const result = await sql.query(cardsQuery, [...params, limitNum, offset]);
|
|
const countResult = await sql.query(countQuery, params);
|
|
|
|
const total = parseInt(countResult.rows[0].total);
|
|
const totalPages = Math.ceil(total / limitNum);
|
|
|
|
// Get filter options for owned cards only
|
|
const filtersQuery = `
|
|
SELECT DISTINCT
|
|
c.game,
|
|
c.rarity,
|
|
c.set_name
|
|
FROM cards c
|
|
INNER JOIN user_cards uc ON c.id = uc.card_id
|
|
WHERE uc.user_id = $1
|
|
ORDER BY c.game, c.rarity, c.set_name
|
|
`;
|
|
|
|
const filtersResult = await sql.query(filtersQuery, [user.userId]);
|
|
|
|
const games = [...new Set(filtersResult.rows.map(row => row.game))].filter(Boolean);
|
|
const rarities = [...new Set(filtersResult.rows.map(row => row.rarity))].filter(Boolean);
|
|
const sets = [...new Set(filtersResult.rows.map(row => row.set_name))].filter(Boolean);
|
|
|
|
res.status(200).json({
|
|
cards: result.rows,
|
|
pagination: {
|
|
page: pageNum,
|
|
limit: limitNum,
|
|
total,
|
|
pages: totalPages
|
|
},
|
|
filters: {
|
|
games,
|
|
rarities,
|
|
sets
|
|
}
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('Error fetching owned cards:', error);
|
|
res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|