Replace @vercel/postgres, Blob, and Upstash with lib/sql.js, MinIO object storage, and CT 102 Redis rate limits. Add Dockerfile for Dokploy deploy, homelab runbooks, Neon data-copy helper, and point CI smoke/visual at the homelab URL instead of Vercel previews. Co-authored-by: Cursor <cursoragent@cursor.com>
118 lines
No EOL
3.6 KiB
JavaScript
118 lines
No EOL
3.6 KiB
JavaScript
import { sql } from '../../../../lib/sql.js';
|
|
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' });
|
|
}
|
|
|
|
const { id: deckId } = req.query;
|
|
|
|
// Verify user owns this deck
|
|
const deckResult = await sql`
|
|
SELECT * FROM decks WHERE id = ${deckId} AND user_id = ${user.userId}
|
|
`;
|
|
|
|
if (deckResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Deck not found or access denied' });
|
|
}
|
|
|
|
if (req.method === 'POST') {
|
|
const { cardId, quantity = 1, condition = 'NM', is_foil = false } = req.body;
|
|
|
|
if (!cardId) {
|
|
return res.status(400).json({ error: 'Card ID is required' });
|
|
}
|
|
|
|
const parsedQuantity = parseInt(quantity, 10);
|
|
if (Number.isNaN(parsedQuantity) || parsedQuantity < 1) {
|
|
return res.status(400).json({ error: 'Quantity must be at least 1' });
|
|
}
|
|
|
|
// Check if card already exists in deck
|
|
const existingResult = await sql`
|
|
SELECT * FROM deck_cards
|
|
WHERE deck_id = ${deckId} AND card_id = ${cardId}
|
|
`;
|
|
|
|
if (existingResult.rows.length > 0) {
|
|
// Update quantity
|
|
const newQuantity = existingResult.rows[0].quantity + parsedQuantity;
|
|
await sql`
|
|
UPDATE deck_cards
|
|
SET quantity = ${newQuantity}, updated_at = CURRENT_TIMESTAMP
|
|
WHERE deck_id = ${deckId} AND card_id = ${cardId}
|
|
`;
|
|
} else {
|
|
// Insert new record
|
|
await sql`
|
|
INSERT INTO deck_cards (deck_id, card_id, quantity)
|
|
VALUES (${deckId}, ${cardId}, ${parsedQuantity})
|
|
`;
|
|
}
|
|
|
|
return res.status(200).json({
|
|
message: 'Card added to deck',
|
|
metadata: { condition, is_foil: Boolean(is_foil) },
|
|
});
|
|
|
|
} else if (req.method === 'GET') {
|
|
// Get cards in deck
|
|
const result = await sql`
|
|
SELECT dc.*, c.name, c.set_name, c.rarity, c.game, c.image_url, c.mana_cost, c.cmc, c.card_type, c.colors
|
|
FROM deck_cards dc
|
|
JOIN cards c ON dc.card_id = c.id
|
|
WHERE dc.deck_id = ${deckId}
|
|
ORDER BY c.name ASC
|
|
`;
|
|
|
|
return res.status(200).json(result.rows);
|
|
|
|
} else if (req.method === 'DELETE') {
|
|
const { cardId, quantity = 1 } = req.body;
|
|
|
|
if (!cardId) {
|
|
return res.status(400).json({ error: 'Card ID is required' });
|
|
}
|
|
|
|
// Check if card exists in deck
|
|
const existingResult = await sql`
|
|
SELECT * FROM deck_cards
|
|
WHERE deck_id = ${deckId} AND card_id = ${cardId}
|
|
`;
|
|
|
|
if (existingResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Card not found in deck' });
|
|
}
|
|
|
|
const currentQuantity = existingResult.rows[0].quantity;
|
|
const newQuantity = currentQuantity - quantity;
|
|
|
|
if (newQuantity <= 0) {
|
|
// Remove card entirely
|
|
await sql`
|
|
DELETE FROM deck_cards
|
|
WHERE deck_id = ${deckId} AND card_id = ${cardId}
|
|
`;
|
|
} else {
|
|
// Update quantity
|
|
await sql`
|
|
UPDATE deck_cards
|
|
SET quantity = ${newQuantity}, updated_at = CURRENT_TIMESTAMP
|
|
WHERE deck_id = ${deckId} AND card_id = ${cardId}
|
|
`;
|
|
}
|
|
|
|
return res.status(200).json({ message: 'Card removed from deck' });
|
|
|
|
} else {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error in deck cards API:', error);
|
|
return res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|