const pool = require('../config/db'); const crypto = require('crypto'); const { sendInvitationEmail } = require('../config/email'); const { logActivity } = require('../services/activityLog'); exports.list = async (req, res) => { try { const [rows] = await pool.query(` SELECT i.*, u.prenom AS invite_par_prenom, u.nom AS invite_par_nom FROM invites i LEFT JOIN users u ON u.id = i.invite_par_id WHERE i.statut_candidature IS NULL OR i.statut_candidature != 'accepte' ORDER BY i.date_club DESC, i.created_at DESC `); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; exports.getById = async (req, res) => { try { const [rows] = await pool.query('SELECT * FROM invites WHERE id = ?', [req.params.id]); if (rows.length === 0) return res.status(404).json({ error: 'Not found' }); res.json(rows[0]); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; exports.search = async (req, res) => { try { const { q } = req.query; if (!q || q.length < 2) return res.json([]); const [rows] = await pool.query(` SELECT prenom, nom, societe, activite, telephone, email, site_web, MAX(visit_number) AS visit_number, COUNT(*) AS total_visits FROM invites WHERE CONCAT(prenom, ' ', nom) LIKE ? GROUP BY prenom, nom, societe, activite, telephone, email, site_web ORDER BY total_visits DESC LIMIT 20 `, [`%${q}%`]); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; const BLOCKED_DATES = ['2026-10-20', '2026-12-15']; exports.create = async (req, res) => { try { const { prenom, nom, societe, activite, telephone, email, site_web, commentaires, date_club } = req.body; if (!prenom || !nom || !telephone || !email || !date_club) { return res.status(400).json({ error: 'Tous les champs sauf commentaires sont requis' }); } if (BLOCKED_DATES.includes(date_club)) { return res.status(403).json({ error: "Cette date n'accepte pas de nouveaux invités" }); } const invite_par_id = req.user.id; // Determine visit_number const [existing] = await pool.query( 'SELECT MAX(visit_number) AS max_vn FROM invites WHERE prenom = ? AND nom = ?', [prenom, nom] ); const visit_number = (existing[0].max_vn || 0) + 1; // Only admin can add 3rd+ visit if (visit_number >= 3 && req.user.role !== 'admin') { return res.status(403).json({ error: 'Seul un administrateur peut ajouter une 3e visite ou plus' }); } const [result] = await pool.query( `INSERT INTO invites (prenom, nom, societe, activite, telephone, email, site_web, commentaires, date_club, invite_par_id, visit_number) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [prenom, nom, societe || null, activite || null, telephone, email, site_web || null, commentaires || null, date_club, invite_par_id, visit_number] ); logActivity(req, 'invite_ajout', { prenom, nom }); res.status(201).json({ id: result.insertId, message: 'Guest added', visit_number }); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; exports.update = async (req, res) => { try { const guestId = req.params.id; const [existing] = await pool.query('SELECT invite_par_id FROM invites WHERE id = ?', [guestId]); if (existing.length === 0) return res.status(404).json({ error: 'Not found' }); if (existing[0].invite_par_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ error: 'You can only edit your own guests' }); } const { prenom, nom, societe, activite, telephone, email, site_web, commentaires, date_club } = req.body; if (BLOCKED_DATES.includes(date_club)) { return res.status(403).json({ error: "Cette date n'accepte pas de nouveaux invités" }); } await pool.query( `UPDATE invites SET prenom=?, nom=?, societe=?, activite=?, telephone=?, email=?, site_web=?, commentaires=?, date_club=? WHERE id=?`, [prenom, nom, societe || null, activite || null, telephone, email, site_web || null, commentaires || null, date_club, guestId] ); logActivity(req, 'invite_modification', { guestId, prenom, nom }); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; exports.remove = async (req, res) => { try { const guestId = req.params.id; const [existing] = await pool.query('SELECT invite_par_id FROM invites WHERE id = ?', [guestId]); if (existing.length === 0) return res.status(404).json({ error: 'Not found' }); if (existing[0].invite_par_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ error: 'Vous ne pouvez supprimer que vos propres invités' }); } await pool.query('DELETE FROM invites WHERE id = ?', [guestId]); logActivity(req, 'invite_suppression', { guestId }); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } }; exports.sendInvitation = async (req, res) => { try { const guestId = req.params.id; const [guests] = await pool.query( `SELECT i.*, u.prenom AS invite_par_prenom, u.nom AS invite_par_nom FROM invites i LEFT JOIN users u ON u.id = i.invite_par_id WHERE i.id = ?`, [guestId] ); if (guests.length === 0) return res.status(404).json({ error: 'Invité non trouvé' }); const guest = guests[0]; if (guest.invite_par_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ error: 'Vous ne pouvez envoyer que vos propres invitations' }); } if (!guest.email) { return res.status(400).json({ error: "Cet invité n'a pas d'adresse email" }); } const token = crypto.randomBytes(32).toString('hex'); const baseUrl = process.env.PUBLIC_URL || 'https://dyna.secourit.fr'; const [inviter] = await pool.query('SELECT prenom, nom FROM users WHERE id = ?', [guest.invite_par_id]); await sendInvitationEmail(guest, inviter[0], token, baseUrl); await pool.query( 'UPDATE invites SET token=?, mail_envoye=1, invitation_sent_at=NOW(), statut="en_attente" WHERE id=?', [token, guestId] ); logActivity(req, 'invite_email_envoye', { guestId, email: guest.email }); } catch (err) { console.error(err); res.status(500).json({ error: "Erreur lors de l'envoi de l'email" }); } }; exports.candidature = async (req, res) => { try { if (req.user.role !== 'admin') return res.status(403).json({ error: 'Admin seulement' }); const guestId = req.params.id; const { action } = req.body; if (!['start', 'accept', 'reject'].includes(action)) { return res.status(400).json({ error: 'Action invalide (start/accept/reject)' }); } const [guests] = await pool.query('SELECT * FROM invites WHERE id = ?', [guestId]); if (guests.length === 0) return res.status(404).json({ error: 'Invité non trouvé' }); const guest = guests[0]; if (action === 'start') { await pool.query( 'UPDATE invites SET statut_candidature = ?, candidature_date = NOW() WHERE id = ?', ['candidature', guestId] ); logActivity(req, 'candidature_demarrage', { guestId, prenom: guest.prenom, nom: guest.nom }); return res.json({ message: 'Candidature en cours', statut_candidature: 'candidature' }); } if (action === 'reject') { await pool.query( 'UPDATE invites SET statut_candidature = ? WHERE id = ?', ['refuse', guestId] ); logActivity(req, 'candidature_refus', { guestId, prenom: guest.prenom, nom: guest.nom }); return res.json({ message: 'Candidature refusée', statut_candidature: 'refuse' }); } if (action === 'accept') { const bcrypt = require('bcrypt'); const hash = await bcrypt.hash('Bienvenue2026', 10); const [result] = await pool.query( `INSERT INTO users (prenom, nom, societe, activite, telephone, email, site_web, role, password_hash, date_adhesion, invite_par_id, actif) VALUES (?, ?, ?, ?, ?, ?, ?, 'member', ?, CURDATE(), ?, 1)`, [guest.prenom, guest.nom, guest.societe || null, guest.activite || null, guest.telephone || null, guest.email || null, guest.site_web || null, hash, guest.invite_par_id] ); await pool.query('DELETE FROM invites WHERE id = ?', [guestId]); logActivity(req, 'candidature_acceptee', { guestId, prenom: guest.prenom, nom: guest.nom }); return res.json({ message: 'Membre créé avec succès', user_id: result.insertId, defaultPassword: 'Bienvenue2026' }); } } catch (err) { if (err.code === 'ER_DUP_ENTRY') { return res.status(400).json({ error: 'Cet email est déjà utilisé par un membre existant' }); } console.error(err); res.status(500).json({ error: 'Erreur serveur' }); } }; exports.nextEventGuestCount = async (req, res) => { try { const [events] = await pool.query( 'SELECT date FROM evenements WHERE date >= CURDATE() ORDER BY date ASC LIMIT 1' ); if (events.length === 0) return res.json({ count: 0, next_event_date: null }); const nextDate = events[0].date; const [rows] = await pool.query( 'SELECT COUNT(*) AS count FROM invites WHERE date_club = ?', [nextDate] ); res.json({ count: rows[0].count, next_event_date: nextDate }); } catch (err) { console.error(err); res.status(500).json({ error: 'Server error' }); } };