2026-08-24 22:01:04 -04:00
|
|
|
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' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.method === 'GET') {
|
|
|
|
|
const result = await sql`
|
feat(designer): inline symbol icons, frame textures, community sharing, print sheets
- {CODE} tokens in description/actions/flavor render as inline symbol
icons (RichText), including inside cost pips
- custom frames gain an optional background texture (upload/replace/
remove via /api/custom-frames/[id]/texture); renders behind panels
- custom games can be shared to the community (is_public): toggle in the
game space, public listing at /community/games, read-only game view,
/api/public/games endpoints (no auth, public rows only)
- /designer/print: multi-card print sheets on US Letter at 300dpi
(63x88mm cards, 3x3 or 2x2, dashed cut guides, full-sheet PNG export)
- migration 1787711511000
2026-08-24 22:48:52 -04:00
|
|
|
SELECT g.id, g.name, g.description, g.is_public, g.created_at, g.updated_at,
|
2026-08-24 22:01:04 -04:00
|
|
|
COUNT(c.id) AS card_count
|
|
|
|
|
FROM custom_games g
|
|
|
|
|
LEFT JOIN custom_cards c ON c.custom_game_id = g.id
|
|
|
|
|
WHERE g.user_id = ${user.userId}
|
|
|
|
|
GROUP BY g.id
|
|
|
|
|
ORDER BY g.name ASC
|
|
|
|
|
`;
|
|
|
|
|
return res.status(200).json({ games: result.rows });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (req.method === 'POST') {
|
|
|
|
|
const name = typeof req.body?.name === 'string' ? req.body.name.trim() : '';
|
|
|
|
|
const description =
|
|
|
|
|
typeof req.body?.description === 'string' ? req.body.description.trim() : null;
|
|
|
|
|
|
|
|
|
|
if (!name) {
|
|
|
|
|
return res.status(400).json({ error: 'Game name is required' });
|
|
|
|
|
}
|
|
|
|
|
if (name.length > 100) {
|
|
|
|
|
return res.status(400).json({ error: 'Game name must be 100 characters or fewer' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const existing = await sql`
|
|
|
|
|
SELECT id FROM custom_games
|
|
|
|
|
WHERE user_id = ${user.userId} AND lower(name) = ${name.toLowerCase()}
|
|
|
|
|
`;
|
|
|
|
|
if (existing.rows.length > 0) {
|
|
|
|
|
return res.status(409).json({ error: 'You already have a game with that name' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const inserted = await sql`
|
|
|
|
|
INSERT INTO custom_games (user_id, name, description)
|
|
|
|
|
VALUES (${user.userId}, ${name}, ${description})
|
|
|
|
|
RETURNING id, name, description, created_at, updated_at
|
|
|
|
|
`;
|
|
|
|
|
const game = { ...inserted.rows[0], card_count: 0 };
|
|
|
|
|
return res.status(201).json({ game });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Custom games API error:', error);
|
|
|
|
|
return res.status(500).json({ error: 'Internal server error' });
|
|
|
|
|
}
|
|
|
|
|
}
|