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