import { getUserFromRequest } from '../../../lib/permission-middleware'; import { matchTextInCatalog } from '../../../lib/card-text-match.js'; import { logScanAttempt } from '../../../lib/card-catalog-match.js'; function formatCardResponse(card, ocrMeta) { return { id: card.id, name: card.name, set_name: card.set_name, set_code: card.set_code, card_number: card.card_number, game: card.game, card_type: card.card_type, rarity: card.rarity, hp: card.power, mana_cost: card.mana_cost, image_url: card.image_url, ocr: ocrMeta, }; } export default async function handler(req, res) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const startedAt = Date.now(); try { const user = await getUserFromRequest(req); if (!user) { return res.status(401).json({ error: 'Authentication required' }); } const { ocrText, ocrConfidence, game } = req.body || {}; if (!ocrText || typeof ocrText !== 'string') { return res.status(400).json({ error: 'ocrText is required' }); } const matchResult = await matchTextInCatalog({ ocrText, game, ocrConfidence }); const latencyMs = Date.now() - startedAt; const ocrMeta = { confidence: ocrConfidence ?? null, rawText: ocrText, query: matchResult.query || ocrText, }; if (matchResult.type === 'escalate') { await logScanAttempt({ userId: user.userId, ocrText, ocrConfidence, layer: 1, resultKind: 'escalate', latencyMs, }); return res.status(200).json({ layer: 1, escalate: true, ocr: ocrMeta, reason: matchResult.reason, }); } if (matchResult.type === 'matched') { await logScanAttempt({ userId: user.userId, ocrText, ocrConfidence, layer: 1, matchedCardId: matchResult.card.id, resultKind: 'matched', latencyMs, }); return res.status(200).json({ layer: 1, escalate: false, isCard: true, card: formatCardResponse(matchResult.card, ocrMeta), isExisting: true, message: matchResult.message, ocr: ocrMeta, }); } await logScanAttempt({ userId: user.userId, ocrText, ocrConfidence, layer: 1, resultKind: 'disambiguation', latencyMs, }); return res.status(200).json({ layer: 1, escalate: false, isCard: true, card: null, matches: matchResult.matches, needsUserSelection: true, message: matchResult.message, ocr: ocrMeta, }); } catch (error) { console.error('[POST /api/cards/identify-by-text]', error); if (String(error.message).includes('pg_trgm') || String(error.message).includes('similarity')) { return res.status(503).json({ error: 'Text matching unavailable — run npm run migrate up (pg_trgm extension).', }); } return res.status(500).json({ error: 'Internal server error' }); } }