Initial commit: Club DynaSQY

This commit is contained in:
2026-08-23 09:04:47 +00:00
commit 0875890dc7
145 changed files with 16947 additions and 0 deletions
+175
View File
@@ -0,0 +1,175 @@
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;