const { Op } = require('sequelize'); const { Utilisateur, sequelize } = require('../models/associations'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const multer = require('multer'); const path = require('path'); const fs = require('fs').promises; // Configuration Multer pour upload de photos const storage = multer.diskStorage({ destination: async (req, file, cb) => { const uploadDir = path.join(__dirname, '../uploads/utilisateurs'); try { await fs.mkdir(uploadDir, { recursive: true }); cb(null, uploadDir); } catch (error) { cb(error); } }, filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9); cb(null, 'user-' + uniqueSuffix + path.extname(file.originalname)); } }); const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 }, // 5MB fileFilter: (req, file, cb) => { const allowedTypes = /jpeg|jpg|png|gif/; const extname = allowedTypes.test(path.extname(file.originalname).toLowerCase()); const mimetype = allowedTypes.test(file.mimetype); if (mimetype && extname) { return cb(null, true); } else { cb(new Error('Seules les images sont autorisées')); } } }); class UtilisateurController { // Lister tous les utilisateurs async getAllUtilisateurs(req, res) { try { const { page = 1, limit = 10, role, statut, search, sort_by = 'cree_le', sort_order = 'DESC' } = req.query; const offset = (page - 1) * limit; const whereConditions = {}; // Filtres if (role) whereConditions.role = role; if (statut) whereConditions.statut = statut; if (search) { whereConditions[Op.or] = [ { nom: { [Op.like]: `%${search}%` } }, { prenom: { [Op.like]: `%${search}%` } }, { email: { [Op.like]: `%${search}%` } } ]; } const { rows: utilisateurs, count } = await Utilisateur.findAndCountAll({ where: whereConditions, attributes: { exclude: ['mot_de_passe', 'token_reset'] }, order: [[sort_by, sort_order]], limit: parseInt(limit), offset: parseInt(offset) }); res.json({ success: true, data: { utilisateurs, pagination: { current_page: parseInt(page), total_pages: Math.ceil(count / limit), total_items: count, items_per_page: parseInt(limit) } } }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération des utilisateurs', error: error.message }); } } // Obtenir un utilisateur par ID async getUtilisateurById(req, res) { try { const { id } = req.params; const utilisateur = await Utilisateur.findByPk(id, { attributes: { exclude: ['mot_de_passe', 'token_reset'] } }); if (!utilisateur) { return res.status(404).json({ success: false, message: 'Utilisateur non trouvé' }); } res.json({ success: true, data: utilisateur }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération de l\'utilisateur', error: error.message }); } } // Créer un nouvel utilisateur async createUtilisateur(req, res) { const transaction = await sequelize.transaction(); try { const { nom, prenom, email, mot_de_passe, telephone, role, date_embauche, salaire, adresse, date_naissance } = req.body; // Vérifier si l'email existe déjà const existingUser = await Utilisateur.findOne({ where: { email } }); if (existingUser) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'Cet email est déjà utilisé' }); } // Créer l'utilisateur const nouveauUtilisateur = await Utilisateur.create({ nom, prenom, email, mot_de_passe, telephone, role: role || 'serveur', date_embauche: date_embauche ? new Date(date_embauche) : null, salaire: salaire ? parseFloat(salaire) : null, adresse, date_naissance: date_naissance ? new Date(date_naissance) : null, photo: req.file ? `/uploads/utilisateurs/${req.file.filename}` : null }, { transaction }); await transaction.commit(); res.status(201).json({ success: true, message: 'Utilisateur créé avec succès', data: nouveauUtilisateur.toSafeJSON() }); } catch (error) { await transaction.rollback(); // Supprimer le fichier uploadé en cas d'erreur if (req.file) { try { await fs.unlink(req.file.path); } catch (unlinkError) { console.error('Erreur lors de la suppression du fichier:', unlinkError); } } res.status(500).json({ success: false, message: 'Erreur lors de la création de l\'utilisateur', error: error.message }); } } // Mettre à jour un utilisateur async updateUtilisateur(req, res) { const transaction = await sequelize.transaction(); try { const { id } = req.params; const updateData = { ...req.body }; // Retirer le mot de passe des données si il est vide if (updateData.mot_de_passe === '') { delete updateData.mot_de_passe; } // Vérifier si l'utilisateur existe const utilisateur = await Utilisateur.findByPk(id); if (!utilisateur) { await transaction.rollback(); return res.status(404).json({ success: false, message: 'Utilisateur non trouvé' }); } // Vérifier l'unicité de l'email si modifié if (updateData.email && updateData.email !== utilisateur.email) { const existingUser = await Utilisateur.findOne({ where: { email: updateData.email, id: { [Op.ne]: id } } }); if (existingUser) { await transaction.rollback(); return res.status(400).json({ success: false, message: 'Cet email est déjà utilisé par un autre utilisateur' }); } } // Ajouter la nouvelle photo si uploadée if (req.file) { // Supprimer l'ancienne photo if (utilisateur.photo) { const oldPhotoPath = path.join(__dirname, '../uploads/utilisateurs', path.basename(utilisateur.photo)); try { await fs.unlink(oldPhotoPath); } catch (error) { console.log('Ancienne photo non trouvée ou déjà supprimée'); } } updateData.photo = `/uploads/utilisateurs/${req.file.filename}`; } // Convertir les dates if (updateData.date_embauche) { updateData.date_embauche = new Date(updateData.date_embauche); } if (updateData.date_naissance) { updateData.date_naissance = new Date(updateData.date_naissance); } if (updateData.salaire) { updateData.salaire = parseFloat(updateData.salaire); } // Mettre à jour l'utilisateur await utilisateur.update(updateData, { transaction }); await transaction.commit(); // Recharger l'utilisateur avec les nouvelles données await utilisateur.reload(); res.json({ success: true, message: 'Utilisateur mis à jour avec succès', data: utilisateur.toSafeJSON() }); } catch (error) { await transaction.rollback(); // Supprimer le nouveau fichier en cas d'erreur if (req.file) { try { await fs.unlink(req.file.path); } catch (unlinkError) { console.error('Erreur lors de la suppression du fichier:', unlinkError); } } res.status(500).json({ success: false, message: 'Erreur lors de la mise à jour de l\'utilisateur', error: error.message }); } } // Supprimer un utilisateur async deleteUtilisateur(req, res) { const transaction = await sequelize.transaction(); try { const { id } = req.params; const utilisateur = await Utilisateur.findByPk(id); if (!utilisateur) { await transaction.rollback(); return res.status(404).json({ success: false, message: 'Utilisateur non trouvé' }); } // Supprimer la photo si elle existe if (utilisateur.photo) { const photoPath = path.join(__dirname, '../uploads/utilisateurs', path.basename(utilisateur.photo)); try { await fs.unlink(photoPath); } catch (error) { console.log('Photo non trouvée ou déjà supprimée'); } } await utilisateur.destroy({ transaction }); await transaction.commit(); res.json({ success: true, message: 'Utilisateur supprimé avec succès' }); } catch (error) { await transaction.rollback(); res.status(500).json({ success: false, message: 'Erreur lors de la suppression de l\'utilisateur', error: error.message }); } } // Changer le statut d'un utilisateur async changeStatut(req, res) { try { const { id } = req.params; const { statut } = req.body; if (!['actif', 'inactif', 'suspendu'].includes(statut)) { return res.status(400).json({ success: false, message: 'Statut invalide' }); } const utilisateur = await Utilisateur.findByPk(id); if (!utilisateur) { return res.status(404).json({ success: false, message: 'Utilisateur non trouvé' }); } await utilisateur.update({ statut }); res.json({ success: true, message: `Statut changé vers "${statut}" avec succès`, data: utilisateur.toSafeJSON() }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors du changement de statut', error: error.message }); } } // Réinitialiser le mot de passe async resetPassword(req, res) { try { const { id } = req.params; const { nouveau_mot_de_passe } = req.body; if (!nouveau_mot_de_passe || nouveau_mot_de_passe.length < 6) { return res.status(400).json({ success: false, message: 'Le mot de passe doit contenir au moins 6 caractères' }); } const utilisateur = await Utilisateur.findByPk(id); if (!utilisateur) { return res.status(404).json({ success: false, message: 'Utilisateur non trouvé' }); } await utilisateur.update({ mot_de_passe: nouveau_mot_de_passe, token_reset: null, token_reset_expire: null }); res.json({ success: true, message: 'Mot de passe réinitialisé avec succès' }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la réinitialisation du mot de passe', error: error.message }); } } // Obtenir les statistiques des utilisateurs async getStats(req, res) { try { const stats = await sequelize.query(` SELECT COUNT(*) as total_utilisateurs, COUNT(CASE WHEN statut = 'actif' THEN 1 END) as actifs, COUNT(CASE WHEN statut = 'inactif' THEN 1 END) as inactifs, COUNT(CASE WHEN statut = 'suspendu' THEN 1 END) as suspendus, COUNT(CASE WHEN role = 'admin' THEN 1 END) as admins, COUNT(CASE WHEN role = 'manager' THEN 1 END) as managers, COUNT(CASE WHEN role = 'serveur' THEN 1 END) as serveurs, COUNT(CASE WHEN role = 'cuisinier' THEN 1 END) as cuisiniers, COUNT(CASE WHEN role = 'caissier' THEN 1 END) as caissiers, COUNT(CASE WHEN derniere_connexion >= DATE_SUB(NOW(), INTERVAL 7 DAY) THEN 1 END) as connectes_7_jours, COUNT(CASE WHEN derniere_connexion >= DATE_SUB(NOW(), INTERVAL 30 DAY) THEN 1 END) as connectes_30_jours FROM utilisateurs WHERE est_actif = 1 `, { type: sequelize.QueryTypes.SELECT }); const recentUsers = await Utilisateur.findAll({ attributes: ['id', 'nom', 'prenom', 'role', 'cree_le'], order: [['cree_le', 'DESC']], limit: 5 }); res.json({ success: true, data: { stats: stats[0], recent_users: recentUsers } }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la récupération des statistiques', error: error.message }); } } // Rechercher des utilisateurs async searchUtilisateurs(req, res) { try { const { q, role, limit = 10 } = req.query; if (!q || q.length < 2) { return res.status(400).json({ success: false, message: 'La recherche doit contenir au moins 2 caractères' }); } const whereConditions = { [Op.and]: [ { est_actif: true }, { [Op.or]: [ { nom: { [Op.like]: `%${q}%` } }, { prenom: { [Op.like]: `%${q}%` } }, { email: { [Op.like]: `%${q}%` } } ] } ] }; if (role) { whereConditions[Op.and].push({ role }); } const utilisateurs = await Utilisateur.findAll({ where: whereConditions, attributes: ['id', 'nom', 'prenom', 'email', 'role', 'photo'], order: [['nom', 'ASC'], ['prenom', 'ASC']], limit: parseInt(limit) }); res.json({ success: true, data: utilisateurs }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la recherche', error: error.message }); } } // Login utilisateur async login(req, res) { try { const { email, mot_de_passe } = req.body; if (!email || !mot_de_passe) { return res.status(400).json({ success: false, message: 'Email et mot de passe requis' }); } // Trouver l'utilisateur par email const utilisateur = await Utilisateur.findOne({ where: { email, est_actif: true, statut: 'actif' } }); if (!utilisateur) { return res.status(401).json({ success: false, message: 'Email ou mot de passe incorrect' }); } // Vérifier le mot de passe const isValid = await utilisateur.verifierMotDePasse(mot_de_passe); if (!isValid) { return res.status(401).json({ success: false, message: 'Email ou mot de passe incorrect' }); } // Mettre à jour la dernière connexion await utilisateur.update({ derniere_connexion: new Date() }); // Générer le token JWT const token = jwt.sign( { userId: utilisateur.id, email: utilisateur.email, role: utilisateur.role }, process.env.JWT_SECRET || 'secret_key', { expiresIn: '24h' } ); res.json({ success: true, message: 'Connexion réussie', data: { token, utilisateur: utilisateur.toSafeJSON() } }); } catch (error) { res.status(500).json({ success: false, message: 'Erreur lors de la connexion', error: error.message }); } } } module.exports = { UtilisateurController: new UtilisateurController(), uploadPhoto: upload.single('photo') };