Replace browser Gemini/OCR with POST /api/scan/identify, add card_submissions review queue, remove user-writable cards INSERT, and surface disambiguation when catalog matching is ambiguous. Co-authored-by: Cursor <cursoragent@cursor.com>
104 lines
3.2 KiB
JavaScript
104 lines
3.2 KiB
JavaScript
import { sql } from '@vercel/postgres';
|
|
import { getUserFromRequest } from '../../../lib/permission-middleware';
|
|
|
|
export default async function handler(req, res) {
|
|
try {
|
|
const user = await getUserFromRequest(req);
|
|
if (!user) {
|
|
return res.status(401).json({ error: 'Authentication required' });
|
|
}
|
|
|
|
if (user.role !== 'admin') {
|
|
return res.status(403).json({ error: 'Admin access required' });
|
|
}
|
|
|
|
if (req.method === 'GET') {
|
|
const { status = 'pending' } = req.query;
|
|
const result = await sql`
|
|
SELECT
|
|
cs.*,
|
|
u.email AS submitter_email
|
|
FROM card_submissions cs
|
|
JOIN users u ON cs.user_id = u.id
|
|
WHERE cs.status = ${status}
|
|
ORDER BY cs.created_at DESC
|
|
LIMIT 100
|
|
`;
|
|
return res.status(200).json({ submissions: result.rows });
|
|
}
|
|
|
|
if (req.method === 'POST') {
|
|
const { submissionId, action } = req.body;
|
|
|
|
if (!submissionId || !['approve', 'reject'].includes(action)) {
|
|
return res.status(400).json({ error: 'submissionId and action (approve|reject) are required' });
|
|
}
|
|
|
|
const submissionResult = await sql`
|
|
SELECT * FROM card_submissions WHERE id = ${submissionId} LIMIT 1
|
|
`;
|
|
|
|
if (submissionResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Submission not found' });
|
|
}
|
|
|
|
const submission = submissionResult.rows[0];
|
|
|
|
if (submission.status !== 'pending') {
|
|
return res.status(400).json({ error: 'Submission has already been reviewed' });
|
|
}
|
|
|
|
if (action === 'reject') {
|
|
await sql`
|
|
UPDATE card_submissions
|
|
SET status = 'rejected', reviewed_by = ${user.userId}, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ${submissionId}
|
|
`;
|
|
return res.status(200).json({ message: 'Submission rejected' });
|
|
}
|
|
|
|
const payload = submission.ocr_payload || {};
|
|
const newCardResult = await sql`
|
|
INSERT INTO cards (
|
|
name, set_name, set_code, card_number, rarity, game,
|
|
mana_cost, card_type, oracle_text, power, verified
|
|
) VALUES (
|
|
${payload.name || 'Unknown'},
|
|
${payload.set || null},
|
|
${payload.setCode || null},
|
|
${payload.cardNumber || null},
|
|
${payload.rarity || null},
|
|
${payload.game || 'UNKNOWN'},
|
|
${payload.manaCost || null},
|
|
${payload.cardType || null},
|
|
${payload.rawText || submission.ocr_text || null},
|
|
${payload.hp || null},
|
|
${true}
|
|
)
|
|
RETURNING *
|
|
`;
|
|
|
|
const newCard = newCardResult.rows[0];
|
|
|
|
await sql`
|
|
UPDATE card_submissions
|
|
SET
|
|
status = 'approved',
|
|
reviewed_by = ${user.userId},
|
|
promoted_card_id = ${newCard.id},
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ${submissionId}
|
|
`;
|
|
|
|
return res.status(201).json({
|
|
message: 'Submission approved and card promoted to catalog',
|
|
card: newCard,
|
|
});
|
|
}
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
} catch (error) {
|
|
console.error('[admin/card-submissions]', error);
|
|
return res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|