288 lines
7.7 KiB
JavaScript
288 lines
7.7 KiB
JavaScript
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;
|