deckhearth/api/cards/find-or-create.js

169 lines
No EOL
4.8 KiB
JavaScript

const { Pool } = require('pg');
const jwt = require('jsonwebtoken');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false,
});
// Middleware to verify user authentication
function verifyAuth(req) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new Error('No token provided');
}
const token = authHeader.substring(7);
const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production';
try {
const decoded = jwt.verify(token, jwtSecret);
return decoded;
} catch (error) {
throw new Error('Invalid token');
}
}
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 {
// Verify authentication
const user = verifyAuth(req);
const {
name,
game,
setName = null,
setCode = null,
rarity = null,
cardType = null,
manaCost = null,
ocrConfidence = null,
ocrRawText = null,
imageUrl = null
} = req.body;
// Validate required fields
if (!name || !game) {
return res.status(400).json({
error: 'Card name and game are required'
});
}
// Try to find existing card first
let searchQuery = `
SELECT id, name, set_name, set_code, rarity, game, card_type, mana_cost,
current_price, stock_image_url, image_url, verified
FROM cards
WHERE LOWER(name) = LOWER($1) AND UPPER(game) = UPPER($2)
`;
let searchParams = [name.trim(), game.trim()];
// If set name is provided, try to match more specifically
if (setName) {
searchQuery += ` AND (set_name IS NULL OR LOWER(set_name) = LOWER($3))`;
searchParams.push(setName.trim());
}
searchQuery += ` ORDER BY verified DESC, created_at DESC LIMIT 1`;
const existingCard = await client.query(searchQuery, searchParams);
if (existingCard.rows.length > 0) {
// Card found, return it
const card = existingCard.rows[0];
res.status(200).json({
success: true,
found: true,
message: 'Card found in database',
card: {
id: card.id,
name: card.name,
setName: card.set_name,
setCode: card.set_code,
rarity: card.rarity,
game: card.game,
cardType: card.card_type,
manaCost: card.mana_cost,
currentPrice: card.current_price,
stockImageUrl: card.stock_image_url,
imageUrl: card.image_url,
verified: card.verified
}
});
} else {
// Card not found, create new one
const insertQuery = `
INSERT INTO cards (
name, game, set_name, set_code, rarity, card_type, mana_cost,
ocr_confidence, ocr_raw_text, image_url, verified, created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
)
RETURNING id, name, set_name, set_code, rarity, game, card_type, mana_cost,
ocr_confidence, ocr_raw_text, image_url, verified, created_at
`;
const insertParams = [
name.trim(),
game.trim().toUpperCase(),
setName?.trim() || null,
setCode?.trim() || null,
rarity?.trim() || null,
cardType?.trim() || null,
manaCost?.trim() || null,
ocrConfidence || null,
ocrRawText?.trim() || null,
imageUrl?.trim() || null,
false // New cards from OCR are unverified by default
];
const newCard = await client.query(insertQuery, insertParams);
const card = newCard.rows[0];
res.status(201).json({
success: true,
found: false,
message: 'New card created from scan data',
card: {
id: card.id,
name: card.name,
setName: card.set_name,
setCode: card.set_code,
rarity: card.rarity,
game: card.game,
cardType: card.card_type,
manaCost: card.mana_cost,
currentPrice: null,
stockImageUrl: null,
imageUrl: card.image_url,
verified: card.verified,
ocrConfidence: card.ocr_confidence,
ocrRawText: card.ocr_raw_text,
createdAt: card.created_at
}
});
}
} catch (error) {
console.error('Find or create card error:', error);
if (error.message === 'No token provided' || error.message === 'Invalid token') {
res.status(401).json({ error: error.message });
} else {
res.status(500).json({
error: 'Internal server error',
details: error.message
});
}
} finally {
client.release();
}
}