const express = require('express'); const Games = require('../models/games'); const { DeveloperEarnings } = require('../models/plays'); const { pool } = require('../models/db'); const { authenticate } = require('../middleware/auth'); const router = express.Router(); // Get developer dashboard router.get('/dashboard', authenticate, async (req, res, next) => { try { const games = await Games.findByCreator(req.user.id); const earnings = await DeveloperEarnings.getByDeveloper(req.user.id); const totalPlays = games.reduce((sum, g) => sum + g.play_count, 0); const publishedGames = games.filter(g => g.status === 'published'); const pendingGames = games.filter(g => g.status === 'pending_review'); const draftGames = games.filter(g => g.status === 'draft'); res.json({ stats: { totalGames: games.length, publishedGames: publishedGames.length, pendingReview: pendingGames.length, drafts: draftGames.length, totalPlays: totalPlays, tokensEarned: parseInt(earnings.total_earned) || 0, currentBalance: req.user.tokens_balance } }); } catch (error) { next(error); } } ); // Get developer's games with detailed stats router.get('/games', authenticate, async (req, res, next) => { try { const games = await Games.findByCreator(req.user.id); res.json({ games: games.map(g => ({ id: g.id, title: g.title, description: g.description, status: g.status, playCount: g.play_count, tokensEarned: g.tokens_earned, averageRating: g.total_ratings > 0 ? (g.rating_sum / g.total_ratings).toFixed(1) : null, totalRatings: g.total_ratings, createdAt: g.created_at, publishedAt: g.published_at, rejectionReason: g.rejection_reason })) }); } catch (error) { next(error); } } ); // Request payout (placeholder - would integrate with payment system) router.post('/request-payout', authenticate, async (req, res, next) => { try { const minPayout = 100; if (req.user.tokens_balance < minPayout) { return res.status(400).json({ error: `Minimum payout is ${minPayout} tokens`, currentBalance: req.user.tokens_balance }); } // TODO: Integrate with payment system // For now, just acknowledge the request res.json({ message: 'Payout request received', amount: req.user.tokens_balance, note: 'Payout processing coming soon!' }); } catch (error) { next(error); } } ); // Get game analytics (creator only) router.get('/games/:gameId/analytics', authenticate, async (req, res, next) => { try { const { gameId } = req.params; // Get the game and verify ownership 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: 'You can only view analytics for your own games' }); } // Run all analytics queries in parallel for performance const [ dailyPlaysResult, ratingDistributionResult, sessionStatsResult, uniquePlayersResult, highScoreDistributionResult, peakHoursResult, highestScoreResult ] = await Promise.all([ // Daily play counts for last 30 days pool.query( `SELECT DATE(started_at) as date, COUNT(*) as count FROM plays WHERE game_id = $1 AND started_at >= CURRENT_DATE - INTERVAL '30 days' GROUP BY DATE(started_at) ORDER BY date ASC`, [gameId] ), // Rating distribution (1-5 stars breakdown) pool.query( `SELECT rating, COUNT(*) as count FROM ratings WHERE game_id = $1 GROUP BY rating ORDER BY rating ASC`, [gameId] ), // Average session duration pool.query( `SELECT AVG(duration_seconds) as avg_duration, MIN(duration_seconds) as min_duration, MAX(duration_seconds) as max_duration, COUNT(*) as total_sessions FROM plays WHERE game_id = $1 AND duration_seconds IS NOT NULL`, [gameId] ), // Total unique players pool.query( `SELECT COUNT(DISTINCT player_id) as unique_players FROM plays WHERE game_id = $1`, [gameId] ), // High score distribution (buckets) pool.query( `SELECT CASE WHEN score < 100 THEN '0-99' WHEN score < 500 THEN '100-499' WHEN score < 1000 THEN '500-999' WHEN score < 5000 THEN '1000-4999' WHEN score < 10000 THEN '5000-9999' ELSE '10000+' END as score_range, COUNT(*) as count FROM high_scores WHERE game_id = $1 GROUP BY CASE WHEN score < 100 THEN '0-99' WHEN score < 500 THEN '100-499' WHEN score < 1000 THEN '500-999' WHEN score < 5000 THEN '1000-4999' WHEN score < 10000 THEN '5000-9999' ELSE '10000+' END ORDER BY MIN(score) ASC`, [gameId] ), // Peak play hours (hour of day distribution) pool.query( `SELECT EXTRACT(HOUR FROM started_at) as hour, COUNT(*) as count FROM plays WHERE game_id = $1 GROUP BY EXTRACT(HOUR FROM started_at) ORDER BY hour ASC`, [gameId] ), // Highest score pool.query( `SELECT MAX(score) as highest_score FROM high_scores WHERE game_id = $1`, [gameId] ) ]); // Build daily plays with date labels (last 30 days filled in) const today = new Date(); const thirtyDaysAgo = new Date(today); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const dailyPlaysMap = new Map( dailyPlaysResult.rows.map(row => [ row.date.toISOString().split('T')[0], parseInt(row.count) ]) ); const dailyPlays = []; for (let d = new Date(thirtyDaysAgo); d <= today; d.setDate(d.getDate() + 1)) { const dateStr = d.toISOString().split('T')[0]; const dayNum = d.getDate(); dailyPlays.push({ date: dateStr, plays: dailyPlaysMap.get(dateStr) || 0, label: dayNum.toString() }); } // Build rating distribution as object {1: count, 2: count, ...} const ratingDistribution = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; ratingDistributionResult.rows.forEach(row => { ratingDistribution[row.rating] = parseInt(row.count); }); // Build peak hours as object {0: count, 1: count, ...23: count} const peakHours = {}; for (let hour = 0; hour < 24; hour++) { peakHours[hour] = 0; } peakHoursResult.rows.forEach(row => { peakHours[parseInt(row.hour)] = parseInt(row.count); }); // Build score distribution as object with range labels const scoreRanges = ['0-99', '100-499', '500-999', '1000-4999', '5000-9999', '10000+']; const scoreDistribution = {}; scoreRanges.forEach(range => { scoreDistribution[range] = 0; }); highScoreDistributionResult.rows.forEach(row => { scoreDistribution[row.score_range] = parseInt(row.count); }); const sessionStats = sessionStatsResult.rows[0]; const avgSessionDuration = sessionStats.avg_duration ? Math.round(parseFloat(sessionStats.avg_duration)) : 0; const uniquePlayers = parseInt(uniquePlayersResult.rows[0].unique_players); const highestScore = highestScoreResult.rows[0].highest_score ? parseInt(highestScoreResult.rows[0].highest_score) : null; const avgRating = game.total_ratings > 0 ? parseFloat((game.rating_sum / game.total_ratings).toFixed(2)) : null; res.json({ dailyPlays, ratingDistribution, peakHours, scoreDistribution, summary: { totalPlays: game.play_count || 0, uniquePlayers, avgSessionDuration, favoriteCount: game.favorite_count || 0, avgRating, totalRatings: game.total_ratings || 0, highestScore, remixCount: game.remix_count || 0, tokensEarned: game.tokens_earned || 0 } }); } catch (error) { next(error); } } ); module.exports = router;