const { Router } = require('express'); const pool = require('../config/db'); const { authenticate } = require('../middleware/auth'); const webPush = require('web-push'); const router = Router(); const publicVapidKey = process.env.VAPID_PUBLIC_KEY; const privateVapidKey = process.env.VAPID_PRIVATE_KEY; if (publicVapidKey && privateVapidKey) { webPush.setVapidDetails('mailto:pierre@secourit.fr', publicVapidKey, privateVapidKey); } router.post('/subscribe', authenticate, async (req, res) => { try { const { endpoint, keys, reminderDays } = req.body; if (!endpoint || !keys?.p256dh || !keys?.auth) { return res.status(400).json({ error: 'endpoint, p256dh, and auth required' }); } await pool.query( `INSERT INTO push_subscriptions (user_id, endpoint, p256dh, auth, reminder_days) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE p256dh = VALUES(p256dh), auth = VALUES(auth), reminder_days = VALUES(reminder_days)`, [req.user.id, endpoint, keys.p256dh, keys.auth, reminderDays || 3] ); res.json({ message: 'Abonnement aux notifications enregistré' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); router.delete('/unsubscribe', authenticate, async (req, res) => { try { const { endpoint } = req.body; if (!endpoint) return res.status(400).json({ error: 'endpoint required' }); await pool.query('DELETE FROM push_subscriptions WHERE user_id = ? AND endpoint = ?', [req.user.id, endpoint]); res.json({ message: 'Désabonné' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); router.get('/subscriptions', authenticate, async (req, res) => { try { const [rows] = await pool.query( 'SELECT id, endpoint, reminder_days, last_sent_at, created_at FROM push_subscriptions WHERE user_id = ?', [req.user.id] ); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); router.get('/vapid-public-key', (req, res) => { res.json({ publicKey: publicVapidKey }); }); router.get('/members-with-push', authenticate, async (req, res) => { if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin seulement' }); try { const [rows] = await pool.query( `SELECT u.id, u.prenom, u.nom, u.societe, (SELECT COUNT(*) FROM push_subscriptions ps WHERE ps.user_id = u.id) AS has_push FROM users u WHERE u.actif = 1 ORDER BY u.prenom` ); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); router.post('/broadcast', authenticate, async (req, res) => { if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin seulement' }); try { const { title, body, url, userIds } = req.body; if (!title || !body) return res.status(400).json({ error: 'Titre et message requis' }); let subs; if (userIds && Array.isArray(userIds) && userIds.length > 0) { const placeholders = userIds.map(() => '?').join(','); [subs] = await pool.query( `SELECT endpoint, p256dh, auth, user_id FROM push_subscriptions WHERE user_id IN (${placeholders})`, userIds ); } else { [subs] = await pool.query('SELECT endpoint, p256dh, auth, user_id FROM push_subscriptions'); } if (subs.length === 0) return res.status(400).json({ error: 'Aucun abonné' }); const [notifResult] = await pool.query( 'INSERT INTO notifications (sender_id, title, body) VALUES (?, ?, ?)', [req.user.id, title.slice(0, 100), body.slice(0, 255)] ); const notifId = notifResult.insertId; const recipientIds = [...new Set(subs.map((s) => s.user_id))]; for (const uid of recipientIds) { await pool.query( 'INSERT IGNORE INTO notification_recipients (notification_id, user_id) VALUES (?, ?)', [notifId, uid] ); } const payload = JSON.stringify({ title: title.slice(0, 100), body: body.slice(0, 255), icon: '/logo.svg', badge: '/favicon.svg', data: { url: '/notifications' }, }); let sent = 0; for (const sub of subs) { try { await webPush.sendNotification( { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } }, payload ); sent++; } catch (err) { if (err.statusCode === 410 || err.statusCode === 404) { await pool.query('DELETE FROM push_subscriptions WHERE endpoint = ?', [sub.endpoint]); } } } res.json({ message: `Notification envoyée à ${sent} abonné(s) sur ${recipientIds.length} destinataire(s)` }); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); router.post('/test', authenticate, async (req, res) => { try { const [subs] = await pool.query( 'SELECT endpoint, p256dh, auth FROM push_subscriptions WHERE user_id = ?', [req.user.id] ); if (subs.length === 0) return res.status(400).json({ error: 'Aucun abonnement trouvé' }); const payload = JSON.stringify({ title: 'Dyna Biz Coignières', body: 'Notification de test ✓', icon: '/logo.svg', badge: '/favicon.svg', data: { url: '/' }, }); for (const sub of subs) { try { await webPush.sendNotification( { endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } }, payload ); } catch (err) { if (err.statusCode === 410 || err.statusCode === 404) { await pool.query('DELETE FROM push_subscriptions WHERE endpoint = ?', [sub.endpoint]); } } } res.json({ message: 'Notification de test envoyée' }); } catch (err) { console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }); module.exports = router;