286 lines
8.7 KiB
JavaScript
286 lines
8.7 KiB
JavaScript
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
|
|
};
|