From 6e0f3e901216e5f1ef6201d7280c4564c45ae2cd Mon Sep 17 00:00:00 2001 From: jwinterm Date: Sun, 1 Feb 2026 15:59:30 -0500 Subject: [PATCH] fix: wallet display bug, add test email endpoint - Fix wallet balance always showing 'Disconnected' in admin dashboard - Always attempt balance fetch even if isHealthy=false - Show error state with message instead of generic 'Disconnected' - Add POST /api/admin/alerts/test-email endpoint to verify Resend config - Move alertService to module scope for endpoint access --- html/admin.html | 27 ++++++++++++++------- src/index.js | 64 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 70 insertions(+), 21 deletions(-) diff --git a/html/admin.html b/html/admin.html index 8a1356f..f6db16d 100644 --- a/html/admin.html +++ b/html/admin.html @@ -498,17 +498,28 @@ // Wallet balance if (overview.walletBalance) { - const bal = overview.walletBalance.unlocked_balance || 0; - const formatted = (bal / 1e12).toFixed(4); - document.getElementById('statBalance').textContent = formatted; - document.getElementById('statUnlocked').textContent = `Unlocked: ${formatted}`; - const walletEl = document.getElementById('walletStatus'); - walletEl.className = 'wallet-status' + (bal < 100000000000 ? ' warning' : ' healthy'); - document.getElementById('walletBalance').textContent = `${formatted} ${window.location.hostname.includes('monerogue') ? 'XMR' : 'WOW'}`; + const cryptoType = window.location.hostname.includes('monerogue') ? 'XMR' : 'WOW'; + + if (overview.walletBalance.error) { + // Wallet RPC error - show error state + document.getElementById('statBalance').textContent = 'Error'; + document.getElementById('statUnlocked').textContent = overview.walletBalance.error; + document.getElementById('statUnlocked').title = overview.walletBalance.error; + walletEl.className = 'wallet-status disconnected'; + document.getElementById('walletBalance').textContent = 'RPC Error'; + } else { + // Wallet connected - show balance + const bal = overview.walletBalance.unlocked_balance || 0; + const formatted = (bal / 1e12).toFixed(4); + document.getElementById('statBalance').textContent = formatted; + document.getElementById('statUnlocked').textContent = `Unlocked: ${formatted}`; + walletEl.className = 'wallet-status' + (bal < 100000000000 ? ' warning' : ' healthy'); + document.getElementById('walletBalance').textContent = `${formatted} ${cryptoType}`; + } } else { document.getElementById('statBalance').textContent = '-'; - document.getElementById('walletBalance').textContent = 'Disconnected'; + document.getElementById('walletBalance').textContent = 'Not configured'; document.getElementById('walletStatus').className = 'wallet-status disconnected'; } diff --git a/src/index.js b/src/index.js index 7e281bd..65b9f01 100644 --- a/src/index.js +++ b/src/index.js @@ -49,6 +49,7 @@ gameModeManager.io = io; // Initialize remaining components const activeGames = new Map(); // Maps socketId to Game objects +let alertService = null; // Initialized later when payment system starts const socketHandlers = new SocketHandlers(io, activeGames, broadcastManager, debugManager, gameModeManager, walletRPCService); // Configure static file serving const htmlPath = path.join(__dirname, '../html'); @@ -201,18 +202,21 @@ app.post('/api/user/:socketId/address', asyncHandler(async (req, res) => { app.get('/health', async (req, res) => { const memUsage = process.memoryUsage(); - // Get wallet balance if healthy + // Get wallet balance - always attempt let walletBalance = null; let lowBalanceWarning = false; const lowBalanceThreshold = parseInt(process.env.LOW_BALANCE_THRESHOLD) || 100000000000; // 0.1 XMR default - if (walletRPCService.isHealthy) { - try { - walletBalance = await walletRPCService.getBalance(); + try { + walletBalance = await walletRPCService.getBalance(); + if (walletBalance.error) { + console.warn('Wallet balance returned error:', walletBalance.error); + } else { lowBalanceWarning = walletBalance.unlocked_balance < lowBalanceThreshold; - } catch (e) { - // Ignore balance fetch errors } + } catch (e) { + console.error('Failed to get wallet balance for health check:', e.message); + walletBalance = { balance: 0, unlocked_balance: 0, error: e.message }; } const health = { @@ -822,6 +826,38 @@ app.post('/api/admin/credits/adjust', adminAuth, asyncHandler(async (req, res) = }); })); +/** + * POST /api/admin/alerts/test-email + * Send a test email to verify alert service configuration + * Requires: X-Admin-Key header + */ +app.post('/api/admin/alerts/test-email', adminAuth, asyncHandler(async (req, res) => { + if (!alertService) { + return res.status(503).json({ + success: false, + message: 'Alert service not initialized. Payment system may not be enabled.' + }); + } + + const result = await alertService.sendAlert('test_email', { + subject: `🧪 Wownerogue Alert Test - ${process.env.NODE_ENV || 'development'}`, + html: ` +

Alert Service Test

+

If you received this email, the alert notifications are working correctly!

+
+

Environment: ${process.env.NODE_ENV || 'development'}

+

Time: ${new Date().toISOString()}

+

Server: ${process.env.CRYPTO_TYPE || 'WOW'}

+ ` + }); + + res.json({ + success: result.sent, + message: result.sent ? 'Test email sent successfully!' : (result.reason || 'Failed to send'), + details: result + }); +})); + /** * GET /api/admin/users/search * Search for users by socket ID prefix or payout address @@ -896,14 +932,16 @@ app.get('/api/admin/stats/overview', adminAuth, asyncHandler(async (req, res) => WHERE created_at > NOW() - INTERVAL '24 hours' `); - // Get wallet balance + // Get wallet balance - always attempt, even if isHealthy is false let walletBalance = null; - if (walletRPCService.isHealthy) { - try { - walletBalance = await walletRPCService.getBalance(); - } catch (e) { - // Ignore + try { + walletBalance = await walletRPCService.getBalance(); + if (walletBalance.error) { + console.warn('Wallet balance returned error:', walletBalance.error); } + } catch (e) { + console.error('Failed to get wallet balance for admin stats:', e.message); + walletBalance = { balance: 0, unlocked_balance: 0, error: e.message }; } const user = userStats.rows[0]; @@ -1515,7 +1553,7 @@ async function startServer() { const maxRetries = Number(process.env.PAYOUT_MAX_RETRIES) || 3; const retryIntervalMs = Number(process.env.PAYOUT_RETRY_INTERVAL_MS) || 300000; // 5 minutes // Initialize alert service for email notifications - const alertService = new AlertService({ + alertService = new AlertService({ walletService: walletRPCService, db: databaseManager, debugManager