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>
91 lines
No EOL
3 KiB
JavaScript
91 lines
No EOL
3 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;
|
|
|
|
if (req.method === 'GET') {
|
|
// Get deck details with cards
|
|
const deckResult = await sql`
|
|
SELECT d.*, u.username as creator_username
|
|
FROM decks d
|
|
JOIN users u ON d.user_id = u.id
|
|
WHERE d.id = ${deckId} AND (d.user_id = ${user.userId} OR d.is_public = true)
|
|
`;
|
|
|
|
if (deckResult.rows.length === 0) {
|
|
return res.status(404).json({ error: 'Deck not found or access denied' });
|
|
}
|
|
|
|
const deck = deckResult.rows[0];
|
|
|
|
// Get deck cards with details
|
|
const cardsResult = await sql`
|
|
SELECT dc.*, c.name, c.set_name, c.rarity, c.mana_cost, c.cmc,
|
|
c.card_type, c.colors, c.image_url, c.oracle_text
|
|
FROM deck_cards dc
|
|
JOIN cards c ON dc.card_id = c.id
|
|
WHERE dc.deck_id = ${deckId}
|
|
ORDER BY c.name ASC
|
|
`;
|
|
|
|
deck.cards = cardsResult.rows;
|
|
deck.card_count = cardsResult.rows.reduce((sum, card) => sum + card.quantity, 0);
|
|
|
|
return res.status(200).json(deck);
|
|
|
|
} else if (req.method === 'PUT') {
|
|
// Update deck (only owner can update)
|
|
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' });
|
|
}
|
|
|
|
const { name, description, format, is_public, commander_id } = req.body;
|
|
|
|
const updatedDeck = await sql`
|
|
UPDATE decks
|
|
SET name = ${name}, description = ${description}, format = ${format},
|
|
is_public = ${is_public}, commander_id = ${commander_id}, updated_at = CURRENT_TIMESTAMP
|
|
WHERE id = ${deckId} AND user_id = ${user.userId}
|
|
RETURNING *
|
|
`;
|
|
|
|
return res.status(200).json(updatedDeck.rows[0]);
|
|
|
|
} else if (req.method === 'DELETE') {
|
|
// Delete deck (only owner can delete)
|
|
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' });
|
|
}
|
|
|
|
// Delete deck cards first (foreign key constraint)
|
|
await sql`DELETE FROM deck_cards WHERE deck_id = ${deckId}`;
|
|
|
|
// Delete deck
|
|
await sql`DELETE FROM decks WHERE id = ${deckId} AND user_id = ${user.userId}`;
|
|
|
|
return res.status(200).json({ message: 'Deck deleted successfully' });
|
|
|
|
} else {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Error in deck API:', error);
|
|
return res.status(500).json({ error: 'Internal server error' });
|
|
}
|
|
}
|