import { sql } from '../../../../lib/sql.js'; import { withAdmin } from '../../../../lib/permission-middleware'; import { checkImportRateLimit } from '../../../../lib/rate-limit.js'; const ALLOWED_UPDATE_FIELDS = new Set([ 'rarity', 'verified', 'game', 'mana_cost', 'cmc', 'card_type', 'edhrec_rank', 'layout', 'released_at', 'reserved', 'reprint' ]); const FIELD_VALIDATORS = { rarity: (v) => typeof v === 'string' && v.length > 0 && v.length <= 50, verified: (v) => typeof v === 'boolean', game: (v) => ['mtg', 'pokemon', 'lorcana'].includes(v), mana_cost: (v) => v === null || (typeof v === 'string' && v.length <= 50), cmc: (v) => v === null || (Number.isInteger(v) && v >= 0 && v <= 20), card_type: (v) => v === null || (typeof v === 'string' && v.length <= 255), edhrec_rank: (v) => v === null || (Number.isInteger(v) && v >= 0), layout: (v) => v === null || (typeof v === 'string' && v.length <= 50), released_at: (v) => v === null || (typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v)), reserved: (v) => typeof v === 'boolean', reprint: (v) => typeof v === 'boolean' }; export default withAdmin(async function handler(req, res, user) { if (req.method !== 'POST') { return res.status(405).json({ error: 'Method not allowed' }); } const { allowed, reset } = await checkImportRateLimit(req, user.userId); if (!allowed) { res.setHeader('Retry-After', Math.ceil((reset - Date.now()) / 1000)); return res.status(429).json({ error: 'Too many attempts. Try again later.' }); } try { const { cardIds, updates } = req.body; if (!Array.isArray(cardIds) || cardIds.length === 0) { return res.status(400).json({ error: 'cardIds array is required' }); } if (!updates || typeof updates !== 'object') { return res.status(400).json({ error: 'updates object is required' }); } // Validate and filter allowed fields const validUpdates = {}; const errors = []; for (const [field, value] of Object.entries(updates)) { if (!ALLOWED_UPDATE_FIELDS.has(field)) { errors.push(`Field '${field}' is not allowed for batch update`); continue; } const validator = FIELD_VALIDATORS[field]; if (validator && !validator(value)) { errors.push(`Invalid value for field '${field}'`); continue; } validUpdates[field] = value; } if (errors.length > 0) { return res.status(400).json({ error: 'Validation failed', details: errors }); } if (Object.keys(validUpdates).length === 0) { return res.status(400).json({ error: 'No valid fields to update' }); } // Safety cap if (cardIds.length > 1000) { return res.status(400).json({ error: 'Maximum 1000 cards per batch operation' }); } // Build dynamic update query const setClauses = []; const values = []; let paramIdx = 1; for (const [field, value] of Object.entries(validUpdates)) { setClauses.push(`${field} = $${paramIdx}`); values.push(value); paramIdx++; } // Add updated_at setClauses.push(`updated_at = CURRENT_TIMESTAMP`); const placeholders = cardIds.map((_, i) => `$${paramIdx + i}`).join(','); values.push(...cardIds); const query = ` UPDATE cards SET ${setClauses.join(', ')} WHERE id IN (${placeholders}) `; const result = await sql.query(query, values); // Log the batch edit for audit console.log(`[admin batch edit] User ${user.email} updated ${result.rowCount} cards:`, validUpdates); res.status(200).json({ success: true, updated: result.rowCount, fields: Object.keys(validUpdates) }); } catch (error) { console.error('Error in batch edit:', error); res.status(500).json({ success: false, error: 'Internal server error', message: error.message }); } });