const { Pool } = require('pg'); const webpush = require('web-push'); // Database connection const pool = new Pool({ user: process.env.DB_USER || 'postgres', host: process.env.DB_HOST || 'localhost', database: process.env.DB_NAME || 'ninjacross', password: process.env.DB_PASSWORD || 'postgres', port: process.env.DB_PORT || 5432, }); // VAPID keys (same as push-service.js) const vapidKeys = { publicKey: 'BJmNVx0C3XeVxeKGTP9c-Z4HcuZNmdk6QdiLocZgCmb-miCS0ESFO3W2TvJlRhhNAShV63pWA5p36BTVSetyTds', privateKey: 'HBdRCtmZUAzsWpVjZ2LDaoWliIPHldAb5ExAt8bvDeg' }; webpush.setVapidDetails( 'mailto:admin@ninjacross.com', vapidKeys.publicKey, vapidKeys.privateKey ); /** * Send push notification to a specific player */ async function sendPushToPlayer(playerId, title, message, data = {}) { try { console.log(`📱 Sending push to player ${playerId}: ${title}`); const result = await pool.query(` SELECT endpoint, p256dh, auth FROM player_subscriptions WHERE player_id = $1 `, [playerId]); if (result.rows.length === 0) { console.log(`❌ No subscription found for player ${playerId}`); return { success: false, reason: 'No subscription' }; } // Send to all subscriptions for this player const results = []; for (const row of result.rows) { const subscription = { endpoint: row.endpoint, keys: { p256dh: row.p256dh, auth: row.auth } }; const payload = JSON.stringify({ title: title, body: message, icon: '/pictures/favicon.ico', badge: '/pictures/favicon.ico', data: { url: '/dashboard.html', timestamp: Date.now(), ...data }, actions: [ { action: 'view', title: 'Dashboard öffnen' } ] }); try { await webpush.sendNotification(subscription, payload); results.push({ success: true }); } catch (error) { console.error(`❌ Error sending to subscription:`, error); results.push({ success: false, error: error.message }); } } const successCount = results.filter(r => r.success).length; console.log(`✅ Push notification sent to ${successCount}/${result.rows.length} subscriptions for player ${playerId}`); return { success: successCount > 0, sent: successCount, total: result.rows.length }; } catch (error) { console.error(`❌ Error sending push to player ${playerId}:`, error); return { success: false, error: error.message }; } } /** * Send achievement notification to player */ async function sendAchievementNotification(playerId, achievement) { const title = `🏆 ${achievement.name}`; const message = achievement.description; return await sendPushToPlayer(playerId, title, message, { type: 'achievement', achievementId: achievement.id, points: achievement.points }); } /** * Send best time notification to player */ async function sendBestTimeNotification(playerId, timeType, locationName, time) { const title = `🏁 ${timeType} Bestzeit!`; const message = `Du hast die beste Zeit in ${locationName} mit ${time} erreicht!`; return await sendPushToPlayer(playerId, title, message, { type: 'best_time', timeType: timeType, location: locationName, time: time }); } /** * Send daily summary notification to player */ async function sendDailySummaryNotification(playerId, summary) { const title = `📊 Dein Tagesrückblick`; const message = `Du hattest ${summary.runs} Läufe heute. Beste Zeit: ${summary.bestTime}`; return await sendPushToPlayer(playerId, title, message, { type: 'daily_summary', runs: summary.runs, bestTime: summary.bestTime }); } /** * Send weekly summary notification to player */ async function sendWeeklySummaryNotification(playerId, summary) { const title = `📈 Deine Wochenzusammenfassung`; const message = `Diese Woche: ${summary.runs} Läufe, ${summary.improvement}% Verbesserung!`; return await sendPushToPlayer(playerId, title, message, { type: 'weekly_summary', runs: summary.runs, improvement: summary.improvement }); } /** * Send notification to all logged in players */ async function sendNotificationToAllPlayers(title, message, data = {}) { try { console.log(`📢 Sending notification to all players: ${title}`); const result = await pool.query(` SELECT ps.player_id, ps.endpoint, ps.p256dh, ps.auth, p.firstname, p.lastname FROM player_subscriptions ps JOIN players p ON ps.player_id = p.id WHERE ps.player_id IS NOT NULL `); if (result.rows.length === 0) { console.log('❌ No player subscriptions found'); return { success: false, reason: 'No subscriptions' }; } const promises = result.rows.map(async (row) => { const subscription = { endpoint: row.endpoint, keys: { p256dh: row.p256dh, auth: row.auth } }; const payload = JSON.stringify({ title: title, body: message, icon: '/pictures/favicon.ico', badge: '/pictures/favicon.ico', data: { url: '/dashboard.html', timestamp: Date.now(), ...data } }); try { await webpush.sendNotification(subscription, payload); return { playerId: row.player_id, playerName: `${row.firstname} ${row.lastname}`, success: true }; } catch (error) { console.error(`❌ Error sending to player ${row.player_id}:`, error); return { playerId: row.player_id, playerName: `${row.firstname} ${row.lastname}`, success: false, error: error.message }; } }); const results = await Promise.all(promises); const successCount = results.filter(r => r.success).length; console.log(`✅ Sent notifications to ${successCount}/${results.length} players`); return { success: true, sent: successCount, total: results.length, results: results }; } catch (error) { console.error('❌ Error sending notifications to all players:', error); return { success: false, error: error.message }; } } /** * Get player statistics for notifications */ async function getPlayerStats(playerId) { try { const result = await pool.query(` SELECT p.firstname, p.lastname, COUNT(t.id) as total_runs, MIN(t.recorded_time) as best_time, COUNT(DISTINCT t.location_id) as locations_visited FROM players p LEFT JOIN times t ON p.id = t.player_id WHERE p.id = $1 GROUP BY p.id, p.firstname, p.lastname `, [playerId]); if (result.rows.length === 0) { return null; } return result.rows[0]; } catch (error) { console.error('Error getting player stats:', error); return null; } } /** * Check if player has push notifications enabled */ async function isPlayerPushEnabled(playerId) { try { const result = await pool.query(` SELECT COUNT(*) as count FROM player_subscriptions WHERE player_id = $1 `, [playerId]); return result.rows[0].count > 0; } catch (error) { console.error('Error checking push status:', error); return false; } } module.exports = { sendPushToPlayer, sendAchievementNotification, sendBestTimeNotification, sendDailySummaryNotification, sendWeeklySummaryNotification, sendNotificationToAllPlayers, getPlayerStats, isPlayerPushEnabled };