You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

587 lines
19 KiB

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')
};