Files
gamercomp/backend/src/routes/developer.js
T
2026-04-30 02:14:25 +00:00

294 lines
8.7 KiB
JavaScript

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;