import { Router } from 'express'; import { authenticate, AuthenticatedRequest, requireAdmin } from '../middleware/auth'; import { prisma } from '../utils/prisma'; import { asyncHandler } from '../middleware/errorHandler'; import { mintTokens } from '../services/solana'; const router = Router(); // Get system settings router.get('/settings', authenticate, requireAdmin, asyncHandler(async (_req: AuthenticatedRequest, res) => { const settings = await prisma.systemSettings.findFirst(); if (!settings) { return res.status(404).json({ error: 'Settings not found' }); } res.json(settings); }) ); // Update system settings router.put('/settings', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { const { creditsPerMinute, minWatchPercent, minWatchMinutes, movieRequestCost, tvRequestCost, newReleaseMultiplier, bonusMultiplierActive, bonusMultiplier } = req.body; const settings = await prisma.systemSettings.update({ where: { id: 'default' }, data: { creditsPerMinute, minWatchPercent, minWatchMinutes, movieRequestCost, tvRequestCost, newReleaseMultiplier, bonusMultiplierActive, bonusMultiplier, updatedBy: req.user!.id } }); res.json(settings); }) ); // Pause minting router.post('/pause', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { await prisma.systemSettings.update({ where: { id: 'default' }, data: { mintingPaused: true, updatedBy: req.user!.id } }); res.json({ message: 'Minting paused' }); }) ); // Resume minting router.post('/resume', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { await prisma.systemSettings.update({ where: { id: 'default' }, data: { mintingPaused: false, updatedBy: req.user!.id } }); res.json({ message: 'Minting resumed' }); }) ); // Get all users router.get('/users', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { const { page = '1', limit = '50', search } = req.query; const pageNum = parseInt(page as string); const limitNum = Math.min(parseInt(limit as string), 100); const skip = (pageNum - 1) * limitNum; const where: any = {}; if (search) { where.OR = [ { plexUsername: { contains: search as string, mode: 'insensitive' } }, { email: { contains: search as string, mode: 'insensitive' } } ]; } const [users, total] = await Promise.all([ prisma.user.findMany({ where, orderBy: { createdAt: 'desc' }, skip, take: limitNum, select: { id: true, plexId: true, plexUsername: true, email: true, isAdmin: true, isActive: true, walletAddress: true, totalEarned: true, totalSpent: true, watchTimeMinutes: true, createdAt: true } }), prisma.user.count({ where }) ]); res.json({ users, pagination: { page: pageNum, limit: limitNum, total, totalPages: Math.ceil(total / limitNum) } }); }) ); // Update user router.put('/users/:id', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { const { isAdmin, isActive } = req.body; const user = await prisma.user.update({ where: { id: req.params.id }, data: { isAdmin, isActive }, select: { id: true, plexUsername: true, isAdmin: true, isActive: true } }); res.json(user); }) ); // Grant bonus credits router.post('/users/:id/bonus', authenticate, requireAdmin, asyncHandler(async (req: AuthenticatedRequest, res) => { const { amount, reason } = req.body; if (!amount || amount <= 0) { return res.status(400).json({ error: 'Valid amount required' }); } const user = await prisma.user.findUnique({ where: { id: req.params.id } }); if (!user) { return res.status(404).json({ error: 'User not found' }); } if (!user.walletAddress) { return res.status(400).json({ error: 'User has no wallet' }); } // Mint bonus tokens const signature = await mintTokens( user.walletAddress, amount, { sessionId: `BONUS-${Date.now()}`, contentTitle: reason || 'Admin Bonus', watchDurationMinutes: 0 } ); if (!signature) { return res.status(500).json({ error: 'Failed to mint bonus' }); } // Create transaction record const transaction = await prisma.transaction.create({ data: { userId: user.id, type: 'BONUS', amount, solanaSignature: signature, description: reason || 'Admin bonus', contentTitle: reason || 'Admin Bonus' } }); // Update user stats await prisma.user.update({ where: { id: user.id }, data: { totalEarned: { increment: amount } } }); res.json({ success: true, amount, solanaSignature: signature, transaction }); }) ); // Get dashboard analytics router.get('/analytics', authenticate, requireAdmin, asyncHandler(async (_req: AuthenticatedRequest, res) => { const sevenDaysAgo = new Date(); sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7); const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); const [ userStats, transactionStats, watchStats, dailyActivity ] = await Promise.all([ // User stats prisma.$queryRaw` SELECT COUNT(*) as total, COUNT(CASE WHEN wallet_address IS NOT NULL THEN 1 END) as with_wallet, COUNT(CASE WHEN created_at >= ${sevenDaysAgo} THEN 1 END) as new_this_week FROM users `, // Transaction stats prisma.$queryRaw` SELECT SUM(CASE WHEN type = 'EARN' THEN amount ELSE 0 END) as total_earned, SUM(CASE WHEN type = 'SPEND' THEN amount ELSE 0 END) as total_spent, COUNT(*) as total_transactions FROM transactions `, // Watch stats prisma.$queryRaw` SELECT SUM(duration) as total_seconds, SUM(credits_earned) as total_credits, COUNT(*) as total_events FROM watch_events WHERE is_processed = true `, // Daily activity (last 30 days) prisma.$queryRaw` SELECT DATE(created_at) as date, COUNT(DISTINCT user_id) as active_users, SUM(CASE WHEN type = 'EARN' THEN amount ELSE 0 END) as earned, SUM(CASE WHEN type = 'SPEND' THEN amount ELSE 0 END) as spent FROM transactions WHERE created_at >= ${thirtyDaysAgo} GROUP BY DATE(created_at) ORDER BY date DESC LIMIT 30 ` ]); res.json({ users: userStats[0], transactions: transactionStats[0], watchStats: watchStats[0], dailyActivity }); }) ); export { router as adminRouter };