const express = require('express'); const { body } = require('express-validator'); const Games = require('../models/games'); const { pool } = require('../models/db'); const { authenticate } = require('../middleware/auth'); const validate = require('../middleware/validate'); const { generateGame, regenerateGame } = require('../utils/claude'); const router = express.Router(); // Create game with AI router.post('/create', authenticate, validate([ body('description') .trim() .isLength({ min: 10, max: 1000 }) .withMessage('Game description must be 10-1000 characters'), body('title') .optional() .trim() .isLength({ min: 1, max: 100 }) ]), async (req, res, next) => { try { const { description, title } = req.body; // Check if user is a guest if (req.user.is_guest) { return res.status(403).json({ error: 'Guests cannot create games. Please create an account first.' }); } // Generate game with Claude const result = await generateGame(description); if (!result.success) { return res.status(500).json({ error: 'Failed to generate game. Please try again.' }); } // Create game in database const gameTitle = title || `Game by ${req.user.username}`; const game = await Games.create({ creatorId: req.user.id, title: gameTitle, description: description, gameCode: result.code, prompt: description }); res.status(201).json({ message: 'Game created successfully', game: { id: game.id, title: game.title, description: game.description, status: game.status, createdAt: game.created_at }, previewCode: result.code, tokensUsed: result.tokensUsed || 0 }); } catch (error) { next(error); } } ); // Get game by ID router.get('/:gameId', authenticate, async (req, res, next) => { try { const { gameId } = req.params; const game = await Games.findById(gameId); if (!game) { return res.status(404).json({ error: 'Game not found' }); } // Only show code if owner or game is published const isOwner = game.creator_id === req.user.id; const isPublished = game.status === 'published'; if (!isOwner && !isPublished) { return res.status(403).json({ error: 'Game not available' }); } // Prevent caching of game code responses res.set({ 'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }); res.json({ game: { id: game.id, title: game.title, description: game.description, gameCode: game.game_code, codeVersion: game.code_version || 1, prompt: game.prompt, creationPrompt: game.creation_prompt, originalPrompt: game.original_prompt, promptWasEdited: game.prompt_was_edited || false, promptEditCount: game.prompt_edit_count || 0, promptAnswers: game.prompt_answers, status: game.status, playCount: game.play_count, totalRatings: game.total_ratings, ratingSum: game.rating_sum, creatorId: game.creator_id, creatorUsername: game.creator_username, creatorDisplayName: game.creator_display_name, createdAt: game.created_at, publishedAt: game.published_at, creatorPlayed: game.creator_played, creatorPlayDuration: game.creator_play_duration || 0, isOwner } }); } catch (error) { next(error); } } ); // Update game router.put('/:gameId', authenticate, validate([ body('title').optional().trim().isLength({ min: 1, max: 100 }), body('description').optional().trim().isLength({ min: 10, max: 1000 }), body('feedback').optional().trim().isLength({ min: 5, max: 1000 }) ]), async (req, res, next) => { try { const { gameId } = req.params; const { title, description, feedback } = req.body; const game = await Games.findById(gameId); if (!game) { return res.status(404).json({ error: 'Game not found' }); } if (game.creator_id !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } if (game.status === 'published') { return res.status(400).json({ error: 'Cannot edit published games' }); } let updatedCode = game.game_code; let tokensUsed = 0; // If feedback provided, regenerate with Claude if (feedback) { const result = await regenerateGame(game.game_code, feedback); if (result.success) { updatedCode = result.code; tokensUsed = result.tokensUsed || 0; } else { return res.status(500).json({ error: 'Failed to regenerate game. Please try again.' }); } } const updated = await Games.update(gameId, { title, description, gameCode: feedback ? updatedCode : undefined }); res.json({ message: 'Game updated', game: { id: updated.id, title: updated.title, description: updated.description, status: updated.status, gameCode: updated.game_code }, tokensUsed }); } catch (error) { next(error); } } ); // Submit for review router.post('/:gameId/submit', authenticate, async (req, res, next) => { try { const { gameId } = req.params; const game = await Games.findById(gameId); if (!game) { return res.status(404).json({ error: 'Game not found' }); } if (game.creator_id !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } if (game.status !== 'draft') { return res.status(400).json({ error: 'Game has already been submitted' }); } const updated = await Games.submit(gameId); res.json({ message: 'Game submitted for review', game: { id: updated.id, status: updated.status } }); } catch (error) { next(error); } } ); // Unpublish game (creator only) router.post('/:gameId/unpublish', authenticate, async (req, res, next) => { try { const { gameId } = req.params; const game = await Games.findById(gameId); if (!game) { return res.status(404).json({ error: 'Game not found' }); } if (game.creator_id !== req.user.id) { return res.status(403).json({ error: 'Not authorized' }); } if (game.status !== 'published') { return res.status(400).json({ error: 'Game is not published' }); } // Unpublish - set status back to testing so they can re-publish await pool.query( `UPDATE games SET status = 'testing', published_at = NULL WHERE id = $1`, [gameId] ); res.json({ message: 'Game unpublished. You can edit and re-publish it.', game: { id: parseInt(gameId), status: 'testing' } }); } catch (error) { next(error); } } ); // Delete game router.delete('/:gameId', authenticate, async (req, res, next) => { try { const { gameId } = req.params; const game = await Games.findById(gameId); if (!game) { return res.status(404).json({ error: 'Game not found' }); } if (game.creator_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ error: 'Not authorized' }); } await Games.delete(gameId); res.json({ message: 'Game deleted' }); } catch (error) { next(error); } } ); module.exports = router;