deckhearth/pages/api/custom-cards/index.js

135 lines
4.4 KiB
JavaScript
Raw Normal View History

import { sql } from '../../../lib/sql.js';
import { getUserFromRequest } from '../../../lib/permission-middleware';
const CUSTOM_GAME = 'Custom';
const CUSTOM_SET = 'Designs';
export default async function handler(req, res) {
try {
const user = await getUserFromRequest(req);
if (!user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (req.method === 'GET') {
const result = await sql`
SELECT id, card_id, name, mana_cost, card_type, rarity,
rules_text, actions, power, toughness, frame_id,
artwork_url, created_at, updated_at
FROM custom_cards
WHERE user_id = ${user.userId}
ORDER BY updated_at DESC
`;
return res.status(200).json({ designs: result.rows });
}
if (req.method === 'POST') {
const design = await createDesign(user.userId, req.body);
if (!design) {
return res.status(400).json({ error: 'Name is required' });
}
return res.status(201).json({ design });
}
return res.status(405).json({ error: 'Method not allowed' });
} catch (error) {
console.error('Custom cards API error:', error);
return res.status(500).json({ error: 'Internal server error' });
}
}
function pickFields(body) {
const str = (v) => (typeof v === 'string' ? v.trim() : null);
return {
name: str(body.name),
manaCost: str(body.mana_cost) || str(body.manaCost),
cardType: str(body.card_type) || str(body.cardType),
rarity: str(body.rarity),
rulesText: str(body.rules_text) || str(body.description),
actions: str(body.actions),
power: str(body.power),
toughness: str(body.toughness),
frameId: str(body.frame_id) || str(body.frameId) || 'classic',
artworkUrl: str(body.artwork_url) || str(body.artworkUrl),
};
}
async function createDesign(userId, body) {
const f = pickFields(body);
if (!f.name) return null;
const inserted = await sql`
INSERT INTO custom_cards
(user_id, name, mana_cost, card_type, rarity, rules_text,
actions, power, toughness, frame_id, artwork_url)
VALUES
(${userId}, ${f.name}, ${f.manaCost}, ${f.cardType}, ${f.rarity},
${f.rulesText}, ${f.actions}, ${f.power}, ${f.toughness},
${f.frameId}, ${f.artworkUrl})
RETURNING *
`;
const design = inserted.rows[0];
// Mirror into the shared catalog so the design shows up in My Cards,
// lists, and decks through the normal card joins.
const cardId = await syncCatalogCard(design, f);
if (cardId) {
await ensureOwnedRow(userId, cardId);
const linked = await sql`
UPDATE custom_cards SET card_id = ${cardId} WHERE id = ${design.id}
RETURNING *
`;
return linked.rows[0];
}
return design;
}
/** Create/update the catalog twin of a custom design; returns card id. */
export async function syncCatalogCard(design, fields) {
const values = {
name: fields.name,
setName: CUSTOM_SET,
setCode: 'DSGN',
rarity: fields.rarity,
game: CUSTOM_GAME,
manaCost: fields.manaCost,
cardType: fields.cardType,
oracleText: [fields.rulesText, fields.actions].filter(Boolean).join('\n\n') || null,
imageUrl: fields.artworkUrl,
};
if (design.card_id) {
const updated = await sql`
UPDATE cards SET
name = ${values.name}, set_name = ${values.setName},
set_code = ${values.setCode}, rarity = ${values.rarity},
mana_cost = ${values.manaCost}, card_type = ${values.cardType},
oracle_text = ${values.oracleText}, image_url = ${values.imageUrl},
updated_at = CURRENT_TIMESTAMP
WHERE id = ${design.card_id}
RETURNING id
`;
if (updated.rows.length > 0) return updated.rows[0].id;
}
const inserted = await sql`
INSERT INTO cards (name, set_name, set_code, rarity, game,
mana_cost, card_type, oracle_text, image_url)
VALUES (${values.name}, ${values.setName}, ${values.setCode},
${values.rarity}, ${values.game}, ${values.manaCost},
${values.cardType}, ${values.oracleText}, ${values.imageUrl})
RETURNING id
`;
return inserted.rows[0].id;
}
/** Idempotently give the designer one copy of their own creation. */
export async function ensureOwnedRow(userId, cardId) {
await sql`
INSERT INTO user_cards (user_id, card_id, quantity)
VALUES (${userId}, ${cardId}, 1)
ON CONFLICT (user_id, card_id, is_foil)
DO NOTHING
`;
}