849 lines
30 KiB
JavaScript
849 lines
30 KiB
JavaScript
const express = require('express');
|
||
const cors = require('cors');
|
||
const sql = require('mssql');
|
||
require('dotenv').config();
|
||
|
||
const app = express();
|
||
const PORT = process.env.PORT || 3000;
|
||
|
||
// Configuration base de données
|
||
const dbConfig = {
|
||
server: process.env.DB_SERVER,
|
||
database: process.env.DB_DATABASE,
|
||
user: process.env.DB_USER,
|
||
password: process.env.DB_PASSWORD,
|
||
options: {
|
||
encrypt: true,
|
||
trustServerCertificate: true,
|
||
enableArithAbort: true
|
||
}
|
||
};
|
||
|
||
// Middleware
|
||
app.use(cors({
|
||
origin: [
|
||
'http://localhost:3001',
|
||
'http://localhost:8000',
|
||
'http://127.0.0.1:3001',
|
||
'https://mygtf.ensup-adm.net',
|
||
'https://mygtf.ensup-adm.net:3001',
|
||
'https://mygtf.ensup-adm.net:8000',
|
||
'http://mygtf.ensup-adm.net',
|
||
'http://nginx'
|
||
],
|
||
credentials: true
|
||
}));
|
||
|
||
app.use(express.json());
|
||
|
||
// Log de toutes les requêtes
|
||
app.use((req, res, next) => {
|
||
console.log(`\n${'='.repeat(80)}`);
|
||
console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
|
||
console.log(`Origin: ${req.get('origin') || 'inconnu'}`);
|
||
if (req.query.user_email || req.query.email) {
|
||
console.log(`Paramètres: ${JSON.stringify(req.query)}`);
|
||
}
|
||
if (req.body && Object.keys(req.body).length > 0) {
|
||
console.log(`Body: ${JSON.stringify(req.body, null, 2)}`);
|
||
}
|
||
console.log(`${'='.repeat(80)}\n`);
|
||
next();
|
||
});
|
||
|
||
// Variable pour stocker la connexion et l'état
|
||
let pool = null;
|
||
let systemStatus = {
|
||
hasFormateurEmailColumn: false,
|
||
hasFormateurLocal: false,
|
||
operatingMode: 'unknown'
|
||
};
|
||
|
||
// Fonction pour se connecter à la base
|
||
async function connectDatabase() {
|
||
try {
|
||
console.log('🔌 Tentative de connexion à la base de données...');
|
||
console.log(`Server: ${dbConfig.server}`);
|
||
console.log(`Database: ${dbConfig.database}`);
|
||
console.log(`User: ${dbConfig.user}`);
|
||
|
||
pool = await sql.connect(dbConfig);
|
||
console.log('✅ Base de données connectée avec succès\n');
|
||
|
||
await checkSystemStatus();
|
||
|
||
return true;
|
||
} catch (error) {
|
||
console.error('❌ Erreur de connexion à la base:', error.message);
|
||
console.error('Stack:', error.stack);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Fonction pour vérifier l'état du système - MODE LOCAL UNIQUEMENT
|
||
async function checkSystemStatus() {
|
||
console.log('\n📊 === DIAGNOSTIC DU SYSTÈME (MODE LOCAL ONLY) ===\n');
|
||
|
||
try {
|
||
// 1. Vérifier si la colonne formateur_email_fk existe
|
||
console.log('1️⃣ Vérification colonne formateur_email_fk...');
|
||
const columnCheck = await pool.request().query(`
|
||
SELECT COUNT(*) as count
|
||
FROM INFORMATION_SCHEMA.COLUMNS
|
||
WHERE TABLE_NAME = 'declarations'
|
||
AND COLUMN_NAME = 'formateur_email_fk'
|
||
`);
|
||
systemStatus.hasFormateurEmailColumn = columnCheck.recordset[0].count > 0;
|
||
console.log(` ${systemStatus.hasFormateurEmailColumn ? '✅' : '❌'} Colonne formateur_email_fk: ${systemStatus.hasFormateurEmailColumn ? 'EXISTE' : 'N\'EXISTE PAS'}`);
|
||
|
||
// 2. Vérifier/Créer la table formateurs_local
|
||
console.log('\n2️⃣ Vérification table formateurs_local...');
|
||
try {
|
||
await pool.request().query(`
|
||
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='formateurs_local' AND xtype='U')
|
||
CREATE TABLE formateurs_local (
|
||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||
Civilite NVARCHAR(10),
|
||
Nom_brut NVARCHAR(255),
|
||
givenname NVARCHAR(100),
|
||
alternateEmailAddress NVARCHAR(255),
|
||
Contrat NVARCHAR(100),
|
||
userPrincipalName NVARCHAR(255) UNIQUE NOT NULL,
|
||
surname NVARCHAR(100),
|
||
mailnickname NVARCHAR(100),
|
||
Password NVARCHAR(255),
|
||
displayName NVARCHAR(255),
|
||
Campus NVARCHAR(50),
|
||
Jobtitle NVARCHAR(100),
|
||
departement NVARCHAR(100) NOT NULL DEFAULT 'Non défini',
|
||
role NVARCHAR(100)
|
||
)
|
||
`);
|
||
|
||
const countLocal = await pool.request().query(`SELECT COUNT(*) as count FROM formateurs_local`);
|
||
systemStatus.hasFormateurLocal = true;
|
||
console.log(' ✅ Table formateurs_local: OK');
|
||
console.log(` 📋 Nombre d'entrées: ${countLocal.recordset[0].count}`);
|
||
|
||
if (countLocal.recordset[0].count > 0) {
|
||
const localEntries = await pool.request().query(`
|
||
SELECT TOP 10 * FROM formateurs_local
|
||
ORDER BY userPrincipalName
|
||
`);
|
||
console.log(' 📄 Derniers formateurs locaux:');
|
||
localEntries.recordset.forEach((f, i) => {
|
||
console.log(` ${i + 1}. ${f.Nom_brut || f.displayName} (${f.userPrincipalName}) - Campus: ${f.Campus}`);
|
||
});
|
||
} else {
|
||
console.log(' ⚠️ Aucun formateur dans la table locale');
|
||
}
|
||
} catch (error) {
|
||
systemStatus.hasFormateurLocal = false;
|
||
console.log(' ⚠️ Table formateurs_local: ERREUR');
|
||
console.log(` Erreur: ${error.message}`);
|
||
}
|
||
|
||
// 3. Déterminer le mode de fonctionnement
|
||
console.log('\n3️⃣ Détermination du mode de fonctionnement...');
|
||
if (systemStatus.hasFormateurEmailColumn && systemStatus.hasFormateurLocal) {
|
||
systemStatus.operatingMode = 'local_only';
|
||
console.log(' ✅ Mode: LOCAL_ONLY (table formateurs_local uniquement)');
|
||
} else if (systemStatus.hasFormateurEmailColumn) {
|
||
systemStatus.operatingMode = 'new_email_only';
|
||
console.log(' ⚠️ Mode: NEW_EMAIL_ONLY (dégradé)');
|
||
} else {
|
||
systemStatus.operatingMode = 'legacy_hash';
|
||
console.log(' ⚠️ Mode: LEGACY_HASH (ancien système)');
|
||
}
|
||
|
||
console.log('\n📊 === RÉSUMÉ DU DIAGNOSTIC ===');
|
||
console.log(` - Colonne formateur_email_fk: ${systemStatus.hasFormateurEmailColumn ? '✅' : '❌'}`);
|
||
console.log(` - Table formateurs_local: ${systemStatus.hasFormateurLocal ? '✅' : '❌'}`);
|
||
console.log(` - Mode de fonctionnement: ${systemStatus.operatingMode}`);
|
||
console.log(` - Vue v_Formateurs_CD: DÉSACTIVÉE (mode local)`);
|
||
console.log('================================\n');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Erreur lors du diagnostic:', error.message);
|
||
console.error('Stack:', error.stack);
|
||
systemStatus.operatingMode = 'legacy_hash';
|
||
}
|
||
}
|
||
|
||
// Route de diagnostic
|
||
app.get('/api/diagnostic', async (req, res) => {
|
||
try {
|
||
await checkSystemStatus();
|
||
|
||
let recommendations = [];
|
||
|
||
switch (systemStatus.operatingMode) {
|
||
case 'local_only':
|
||
recommendations.push('✅ Système en mode LOCAL - utilisation de la table formateurs_local');
|
||
recommendations.push('ℹ️ La vue v_Formateurs_CD est désactivée');
|
||
break;
|
||
case 'new_email_only':
|
||
recommendations.push('⚠️ Mode dégradé - sauvegarde par email uniquement');
|
||
recommendations.push('💡 Créer et remplir la table formateurs_local');
|
||
break;
|
||
case 'legacy_hash':
|
||
recommendations.push('🔄 Mode compatibilité - utilise l\'ancien système');
|
||
recommendations.push('💡 Appliquer la migration avec POST /api/migrate');
|
||
break;
|
||
}
|
||
|
||
res.json({
|
||
systemStatus,
|
||
recommendations,
|
||
currentMode: systemStatus.operatingMode
|
||
});
|
||
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Route pour appliquer la migration
|
||
app.post('/api/migrate', async (req, res) => {
|
||
try {
|
||
const steps = [];
|
||
|
||
if (!systemStatus.hasFormateurEmailColumn) {
|
||
try {
|
||
await pool.request().query(`
|
||
ALTER TABLE [dbo].[declarations]
|
||
ADD [formateur_email_fk] [nvarchar](255) NULL
|
||
`);
|
||
steps.push('✅ Colonne formateur_email_fk ajoutée');
|
||
} catch (error) {
|
||
if (!error.message.includes('already exists')) {
|
||
throw error;
|
||
}
|
||
steps.push('ℹ️ Colonne formateur_email_fk déjà existante');
|
||
}
|
||
}
|
||
|
||
try {
|
||
await pool.request().query(`
|
||
CREATE NONCLUSTERED INDEX [IX_declarations_formateur_email_fk]
|
||
ON [dbo].[declarations] ([formateur_email_fk])
|
||
`);
|
||
steps.push('✅ Index créé');
|
||
} catch (error) {
|
||
if (error.message.includes('already exists')) {
|
||
steps.push('ℹ️ Index déjà existant');
|
||
} else {
|
||
steps.push(`⚠️ Erreur index: ${error.message}`);
|
||
}
|
||
}
|
||
|
||
await checkSystemStatus();
|
||
|
||
res.json({
|
||
success: true,
|
||
steps,
|
||
newStatus: systemStatus,
|
||
message: `Migration appliquée - Mode: ${systemStatus.operatingMode}`
|
||
});
|
||
|
||
} catch (error) {
|
||
res.status(500).json({
|
||
success: false,
|
||
error: error.message,
|
||
message: 'Erreur lors de la migration'
|
||
});
|
||
}
|
||
});
|
||
|
||
// Route de test
|
||
app.get('/api/test', (req, res) => {
|
||
res.json({
|
||
message: 'Le serveur fonctionne en mode LOCAL ONLY !',
|
||
timestamp: new Date().toISOString(),
|
||
systemStatus
|
||
});
|
||
});
|
||
|
||
// Route pour les formateurs - MODE LOCAL SIMPLIFIÉ
|
||
app.get('/api/formateurs', async (req, res) => {
|
||
console.log('\n🔍 === ROUTE /api/formateurs (LOCAL ONLY - SIMPLIFIÉ) ===');
|
||
|
||
const { email, search } = req.query;
|
||
console.log(`📥 Paramètres reçus:`);
|
||
console.log(` - email: ${email || 'non fourni'}`);
|
||
console.log(` - search: ${search || 'non fourni'}`);
|
||
|
||
try {
|
||
// Requête simple et directe
|
||
let query = `
|
||
SELECT
|
||
userPrincipalName as email,
|
||
ISNULL(Nom_brut, displayName) as nom_complet,
|
||
Campus,
|
||
Contrat
|
||
FROM formateurs_local
|
||
`;
|
||
|
||
const request = pool.request();
|
||
|
||
if (email) {
|
||
query += ` WHERE userPrincipalName = @email`;
|
||
request.input('email', sql.NVarChar, email);
|
||
console.log(`🔎 Recherche par email: ${email}`);
|
||
} else if (search) {
|
||
query += ` WHERE Nom_brut LIKE @search OR displayName LIKE @search OR userPrincipalName LIKE @search`;
|
||
request.input('search', sql.NVarChar, `%${search}%`);
|
||
console.log(`🔎 Recherche par texte: ${search}`);
|
||
}
|
||
|
||
query += ` ORDER BY ISNULL(Nom_brut, displayName)`;
|
||
|
||
console.log('🚀 Exécution de la requête SQL...');
|
||
console.log(`📝 Query: ${query.substring(0, 200)}...`);
|
||
|
||
const result = await request.query(query);
|
||
console.log(`✅ ${result.recordset.length} formateur(s) trouvé(s)`);
|
||
|
||
if (result.recordset.length > 0) {
|
||
console.log('📄 Premier résultat:', result.recordset[0]);
|
||
}
|
||
|
||
return res.json({
|
||
success: true,
|
||
data: result.recordset,
|
||
source: 'formateurs_local',
|
||
mode: 'local_only'
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('❌ ========== ERREUR /api/formateurs ==========');
|
||
console.error(`Message: ${error.message}`);
|
||
console.error(`Code: ${error.code}`);
|
||
console.error(`Stack: ${error.stack}`);
|
||
console.error('==============================================');
|
||
|
||
return res.status(500).json({
|
||
success: false,
|
||
error: 'Erreur lors de la récupération des formateurs',
|
||
message: error.message,
|
||
code: error.code
|
||
});
|
||
}
|
||
});
|
||
|
||
// Conversion Time
|
||
const toSQLTime = (timeStr) => {
|
||
if (!timeStr) return null;
|
||
const [hours, minutes] = timeStr.split(':').map(Number);
|
||
return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:00`;
|
||
};
|
||
|
||
// Route pour sauvegarder une déclaration
|
||
app.post('/api/save_declaration', async (req, res) => {
|
||
console.log('\n💾 === ROUTE /api/save_declaration ===');
|
||
|
||
try {
|
||
const { id, date, activityType, hours, description, user, startTime, endTime } = req.body;
|
||
|
||
console.log('📥 Données reçues:');
|
||
console.log(` - id: ${id || 'nouveau'}`);
|
||
console.log(` - date: ${date}`);
|
||
console.log(` - user.email: ${user?.email}`);
|
||
|
||
if (!user?.email) {
|
||
console.error('❌ Email requis manquant');
|
||
return res.status(400).json({ error: 'Email requis' });
|
||
}
|
||
|
||
const heureDebutSQL = toSQLTime(startTime);
|
||
const heureFinSQL = toSQLTime(endTime);
|
||
|
||
// Récupérer l'ID du type "preparation"
|
||
const typeResult = await pool.request()
|
||
.query("SELECT id FROM types_demandes WHERE libelle = 'preparation'");
|
||
|
||
if (typeResult.recordset.length === 0) {
|
||
return res.status(500).json({
|
||
error: 'Configuration manquante: le type "preparation" n\'existe pas.'
|
||
});
|
||
}
|
||
|
||
const typeDemandeId = typeResult.recordset[0].id;
|
||
|
||
// Vérifier le campus et la clôture
|
||
const moisAnnee = date.substring(0, 7);
|
||
let campusFormateur = null;
|
||
|
||
console.log(`🏫 Recherche du campus pour: ${user.email}`);
|
||
try {
|
||
const formateurInfo = await pool.request()
|
||
.input('email', sql.NVarChar, user.email)
|
||
.query('SELECT Campus FROM formateurs_local WHERE userPrincipalName = @email');
|
||
|
||
if (formateurInfo.recordset.length > 0) {
|
||
campusFormateur = formateurInfo.recordset[0].Campus;
|
||
console.log(` ✅ Campus trouvé: ${campusFormateur}`);
|
||
}
|
||
} catch (error) {
|
||
console.log(' ⚠️ Erreur récupération campus:', error.message);
|
||
}
|
||
|
||
// Vérifier clôture
|
||
try {
|
||
const clotureCheck = await pool.request()
|
||
.input('mois_annee', sql.VarChar, moisAnnee)
|
||
.input('campus', sql.VarChar, campusFormateur)
|
||
.query(`
|
||
SELECT id, date_debut, date_fin, commentaire, campus
|
||
FROM clotures_saisie
|
||
WHERE mois_annee = @mois_annee
|
||
AND (campus = @campus OR campus IS NULL)
|
||
`);
|
||
|
||
if (clotureCheck.recordset.length > 0) {
|
||
const cloture = clotureCheck.recordset[0];
|
||
const campusMessage = cloture.campus ? ` (Campus: ${cloture.campus})` : ' (Tous les campus)';
|
||
return res.status(403).json({
|
||
success: false,
|
||
error: `⛔ Cette période est clôturée${campusMessage}.`,
|
||
cloture: cloture
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.log('⚠️ Pas de vérification de clôture');
|
||
}
|
||
|
||
// Validation formateur
|
||
console.log('👤 Vérification formateur dans formateurs_local...');
|
||
const formateur = await pool.request()
|
||
.input('email', sql.NVarChar, user.email)
|
||
.query('SELECT userPrincipalName FROM formateurs_local WHERE userPrincipalName = @email');
|
||
|
||
if (formateur.recordset.length === 0) {
|
||
console.error(`❌ Formateur ${user.email} non trouvé dans formateurs_local`);
|
||
return res.status(403).json({
|
||
error: 'Formateur non trouvé dans la table locale. Veuillez contacter l\'administrateur.',
|
||
email: user.email
|
||
});
|
||
}
|
||
|
||
const formateurEmail = formateur.recordset[0].userPrincipalName;
|
||
console.log(`✅ Formateur validé: ${formateurEmail}`);
|
||
|
||
if (id) {
|
||
// MODIFICATION
|
||
console.log(`✏️ MODIFICATION de la déclaration ID: ${id}`);
|
||
await pool.request()
|
||
.input('id', sql.Int, id)
|
||
.input('typeDemandeId', sql.Int, typeDemandeId)
|
||
.input('hours', sql.Float, hours)
|
||
.input('description', sql.NVarChar, description || null)
|
||
.input('heure_debut', sql.VarChar, heureDebutSQL)
|
||
.input('heure_fin', sql.VarChar, heureFinSQL)
|
||
.query(`
|
||
UPDATE declarations
|
||
SET type_demande_id=@typeDemandeId, duree=@hours, description=@description,
|
||
heure_debut=@heure_debut, heure_fin=@heure_fin
|
||
WHERE id=@id
|
||
`);
|
||
console.log('✅ Déclaration modifiée');
|
||
} else {
|
||
// CRÉATION
|
||
console.log(`➕ CRÉATION nouvelle déclaration`);
|
||
await pool.request()
|
||
.input('formateurEmail', sql.NVarChar, formateurEmail)
|
||
.input('typeDemandeId', sql.Int, typeDemandeId)
|
||
.input('date', sql.Date, date)
|
||
.input('hours', sql.Float, hours)
|
||
.input('description', sql.NVarChar, description || null)
|
||
.input('heure_debut', sql.VarChar, heureDebutSQL)
|
||
.input('heure_fin', sql.VarChar, heureFinSQL)
|
||
.query(`
|
||
INSERT INTO declarations
|
||
(formateur_email_fk, type_demande_id, date, duree, description, heure_debut, heure_fin)
|
||
VALUES
|
||
(@formateurEmail, @typeDemandeId, @date, @hours, @description, @heure_debut, @heure_fin)
|
||
`);
|
||
console.log('✅ Déclaration créée');
|
||
}
|
||
|
||
res.json({
|
||
success: true,
|
||
message: 'Déclaration sauvegardée'
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error("❌ ERREUR save_declaration:", error.message);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Récupérer déclarations par email
|
||
app.get('/api/get_declarations_by_email', async (req, res) => {
|
||
console.log('\n📋 === ROUTE /api/get_declarations_by_email ===');
|
||
|
||
try {
|
||
const { email } = req.query;
|
||
console.log(`📥 Email: ${email}`);
|
||
|
||
if (!email) {
|
||
return res.status(400).json({ error: 'Email requis' });
|
||
}
|
||
|
||
const query = `
|
||
SELECT
|
||
d.id,
|
||
d.formateur_email_fk AS formateur_email,
|
||
f.Nom_brut AS formateur_nom,
|
||
f.Campus,
|
||
td.libelle AS activityType,
|
||
d.date,
|
||
d.duree,
|
||
d.heure_debut,
|
||
d.heure_fin,
|
||
d.description,
|
||
d.status
|
||
FROM declarations d
|
||
INNER JOIN types_demandes td ON d.type_demande_id = td.id
|
||
LEFT JOIN formateurs_local f ON d.formateur_email_fk = f.userPrincipalName
|
||
WHERE d.formateur_email_fk = @email
|
||
ORDER BY d.date DESC
|
||
`;
|
||
|
||
const result = await pool.request()
|
||
.input('email', sql.NVarChar, email)
|
||
.query(query);
|
||
|
||
console.log(`✅ ${result.recordset.length} déclaration(s) trouvée(s)`);
|
||
res.json(result.recordset);
|
||
|
||
} catch (error) {
|
||
console.error('❌ ERREUR:', error.message);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Récupérer toutes les déclarations
|
||
app.get('/api/get_declarations', async (req, res) => {
|
||
console.log('\n📋 === ROUTE /api/get_declarations ===');
|
||
|
||
try {
|
||
const { user_email, admin } = req.query;
|
||
|
||
if (user_email && !admin) {
|
||
return app._router.handle({ ...req, query: { email: user_email } }, res);
|
||
}
|
||
|
||
const query = `
|
||
SELECT
|
||
d.id,
|
||
d.formateur_email_fk AS formateur_email,
|
||
f.Nom_brut AS formateur_nom,
|
||
f.Campus,
|
||
td.libelle AS activityType,
|
||
d.date,
|
||
d.duree,
|
||
d.heure_debut,
|
||
d.heure_fin,
|
||
d.description,
|
||
d.status
|
||
FROM declarations d
|
||
INNER JOIN types_demandes td ON d.type_demande_id = td.id
|
||
LEFT JOIN formateurs_local f ON d.formateur_email_fk = f.userPrincipalName
|
||
ORDER BY d.date DESC
|
||
`;
|
||
|
||
const result = await pool.request().query(query);
|
||
console.log(`✅ ${result.recordset.length} déclaration(s)`);
|
||
res.json(result.recordset);
|
||
|
||
} catch (error) {
|
||
console.error('❌ ERREUR:', error.message);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Supprimer une déclaration
|
||
app.delete('/api/delete_declaration/:id', async (req, res) => {
|
||
console.log('\n🗑️ === ROUTE /api/delete_declaration ===');
|
||
|
||
try {
|
||
const { id } = req.params;
|
||
|
||
if (!id) {
|
||
return res.status(400).json({ error: 'ID requis' });
|
||
}
|
||
|
||
const existing = await pool.request()
|
||
.input('id', sql.Int, id)
|
||
.query('SELECT * FROM declarations WHERE id = @id');
|
||
|
||
if (existing.recordset.length === 0) {
|
||
return res.status(404).json({ error: 'Déclaration non trouvée' });
|
||
}
|
||
|
||
await pool.request()
|
||
.input('id', sql.Int, id)
|
||
.query('DELETE FROM declarations WHERE id = @id');
|
||
|
||
console.log(`✅ Déclaration ${id} supprimée`);
|
||
res.json({ success: true, message: 'Déclaration supprimée' });
|
||
|
||
} catch (error) {
|
||
console.error('❌ ERREUR:', error.message);
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Log connexion
|
||
app.post('/api/log-connexion', async (req, res) => {
|
||
const quickResponse = () => {
|
||
return res.json({
|
||
success: true,
|
||
message: 'Connexion autorisée',
|
||
logged: false
|
||
});
|
||
};
|
||
|
||
try {
|
||
const { email, nom, statut, messageErreur, campus } = req.body;
|
||
|
||
if (!email) {
|
||
return quickResponse();
|
||
}
|
||
|
||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress || 'unknown';
|
||
const userAgent = req.headers['user-agent'] || 'unknown';
|
||
|
||
let estDansVue = false;
|
||
try {
|
||
const checkFormateur = await pool.request()
|
||
.input('email', sql.NVarChar, email)
|
||
.query(`SELECT COUNT(*) as count FROM formateurs_local WHERE userPrincipalName = @email`);
|
||
estDansVue = checkFormateur.recordset[0].count > 0;
|
||
} catch (error) {
|
||
console.error('⚠️ Erreur vérification formateur');
|
||
}
|
||
|
||
try {
|
||
await pool.request().query(`
|
||
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='logs_connexion' AND xtype='U')
|
||
CREATE TABLE logs_connexion (
|
||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||
email_utilisateur NVARCHAR(255) NOT NULL,
|
||
nom_utilisateur NVARCHAR(255),
|
||
date_connexion DATETIME DEFAULT GETDATE(),
|
||
adresse_ip NVARCHAR(50),
|
||
user_agent NVARCHAR(500),
|
||
statut_connexion VARCHAR(20),
|
||
message_erreur NVARCHAR(MAX),
|
||
est_dans_vue BIT DEFAULT 0,
|
||
campus NVARCHAR(50)
|
||
)
|
||
`);
|
||
} catch (error) {
|
||
// Table existe
|
||
}
|
||
|
||
try {
|
||
await pool.request()
|
||
.input('email', sql.NVarChar, email)
|
||
.input('nom', sql.NVarChar, nom || null)
|
||
.input('ip', sql.NVarChar, ip.substring(0, 50))
|
||
.input('userAgent', sql.NVarChar, userAgent.substring(0, 500))
|
||
.input('statut', sql.VarChar, (statut || 'success').substring(0, 20))
|
||
.input('messageErreur', sql.NVarChar, messageErreur || null)
|
||
.input('estDansVue', sql.Bit, estDansVue)
|
||
.input('campus', sql.NVarChar, campus || null)
|
||
.query(`
|
||
INSERT INTO logs_connexion
|
||
(email_utilisateur, nom_utilisateur, adresse_ip, user_agent,
|
||
statut_connexion, message_erreur, est_dans_vue, campus)
|
||
VALUES
|
||
(@email, @nom, @ip, @userAgent, @statut, @messageErreur, @estDansVue, @campus)
|
||
`);
|
||
|
||
return res.json({
|
||
success: true,
|
||
message: 'Connexion enregistrée',
|
||
logged: true,
|
||
estDansVue: estDansVue
|
||
});
|
||
|
||
} catch (error) {
|
||
return quickResponse();
|
||
}
|
||
|
||
} catch (error) {
|
||
return quickResponse();
|
||
}
|
||
});
|
||
|
||
// Logs connexion
|
||
app.get('/api/logs-connexion', async (req, res) => {
|
||
try {
|
||
const { email, dateDebut, dateFin, limit = 100, statut, campus } = req.query;
|
||
|
||
let query = `SELECT * FROM logs_connexion WHERE 1=1`;
|
||
const request = pool.request();
|
||
|
||
if (email) {
|
||
query += ` AND email_utilisateur = @email`;
|
||
request.input('email', sql.NVarChar, email);
|
||
}
|
||
if (dateDebut) {
|
||
query += ` AND date_connexion >= @dateDebut`;
|
||
request.input('dateDebut', sql.DateTime, dateDebut);
|
||
}
|
||
if (dateFin) {
|
||
query += ` AND date_connexion <= @dateFin`;
|
||
request.input('dateFin', sql.DateTime, dateFin);
|
||
}
|
||
if (statut) {
|
||
query += ` AND statut_connexion = @statut`;
|
||
request.input('statut', sql.VarChar, statut);
|
||
}
|
||
if (campus) {
|
||
query += ` AND campus = @campus`;
|
||
request.input('campus', sql.NVarChar, campus);
|
||
}
|
||
|
||
query += ` ORDER BY date_connexion DESC`;
|
||
|
||
const limitNum = parseInt(limit);
|
||
if (limitNum > 0) {
|
||
query = `SELECT TOP ${limitNum} * FROM (${query}) AS subquery`;
|
||
}
|
||
|
||
const result = await request.query(query);
|
||
res.json({ success: true, logs: result.recordset, total: result.recordset.length });
|
||
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message });
|
||
}
|
||
});
|
||
|
||
// Stats connexion
|
||
app.get('/api/stats-connexion', async (req, res) => {
|
||
try {
|
||
const { dateDebut, dateFin } = req.query;
|
||
const request = pool.request();
|
||
let whereClause = '';
|
||
|
||
if (dateDebut) {
|
||
whereClause += ' AND date_connexion >= @dateDebut';
|
||
request.input('dateDebut', sql.DateTime, dateDebut);
|
||
}
|
||
if (dateFin) {
|
||
whereClause += ' AND date_connexion <= @dateFin';
|
||
request.input('dateFin', sql.DateTime, dateFin);
|
||
}
|
||
|
||
const stats = await request.query(`
|
||
SELECT
|
||
COUNT(*) as total_connexions,
|
||
COUNT(DISTINCT email_utilisateur) as utilisateurs_uniques,
|
||
SUM(CASE WHEN statut_connexion = 'success' THEN 1 ELSE 0 END) as connexions_reussies,
|
||
SUM(CASE WHEN statut_connexion = 'failed' THEN 1 ELSE 0 END) as connexions_echouees
|
||
FROM logs_connexion
|
||
WHERE 1=1 ${whereClause}
|
||
`);
|
||
|
||
res.json({ success: true, statistiques: stats.recordset[0] });
|
||
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message });
|
||
}
|
||
});
|
||
|
||
// Vérifier clôture
|
||
app.get('/api/check-cloture', async (req, res) => {
|
||
try {
|
||
const { date, campus } = req.query;
|
||
|
||
if (!date) {
|
||
return res.status(400).json({ error: 'Date requise' });
|
||
}
|
||
|
||
const moisAnnee = date.substring(0, 7);
|
||
|
||
try {
|
||
await pool.request().query(`
|
||
IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='clotures_saisie' AND xtype='U')
|
||
CREATE TABLE clotures_saisie (
|
||
id INT IDENTITY(1,1) PRIMARY KEY,
|
||
mois_annee VARCHAR(7) NOT NULL,
|
||
date_debut DATE NOT NULL,
|
||
date_fin DATE NOT NULL,
|
||
date_cloture DATETIME DEFAULT GETDATE(),
|
||
commentaire NVARCHAR(500),
|
||
campus VARCHAR(50)
|
||
)
|
||
`);
|
||
} catch (error) {
|
||
// Table existe
|
||
}
|
||
|
||
const result = await pool.request()
|
||
.input('mois_annee', sql.VarChar, moisAnnee)
|
||
.input('campus', sql.VarChar, campus || null)
|
||
.query(`
|
||
SELECT * FROM clotures_saisie
|
||
WHERE mois_annee = @mois_annee
|
||
AND (campus = @campus OR campus IS NULL OR @campus IS NULL)
|
||
`);
|
||
|
||
res.json({
|
||
cloture: result.recordset.length > 0,
|
||
details: result.recordset[0] || null
|
||
});
|
||
} catch (error) {
|
||
res.status(500).json({ error: error.message });
|
||
}
|
||
});
|
||
|
||
// Liste clôtures
|
||
app.get('/api/clotures', async (req, res) => {
|
||
try {
|
||
const result = await pool.request().query(`
|
||
SELECT * FROM clotures_saisie
|
||
ORDER BY date_cloture DESC
|
||
`);
|
||
res.json({ success: true, clotures: result.recordset });
|
||
} catch (error) {
|
||
res.status(500).json({ success: false, error: error.message });
|
||
}
|
||
});
|
||
|
||
// Démarrage du serveur
|
||
async function startServer() {
|
||
const dbConnected = await connectDatabase();
|
||
|
||
if (!dbConnected) {
|
||
console.log('❌ Impossible de démarrer sans base de données');
|
||
return;
|
||
}
|
||
|
||
app.listen(PORT, '0.0.0.0', () => {
|
||
|
||
console.log(`\n${'='.repeat(80)}`);
|
||
console.log(`🚀 SERVEUR DÉMARRÉ - MODE LOCAL ONLY`);
|
||
console.log(`${'='.repeat(80)}`);
|
||
console.log(`URL: http://localhost:${PORT}`);
|
||
console.log(`Mode: ${systemStatus.operatingMode}`);
|
||
console.log(`Source: formateurs_local UNIQUEMENT`);
|
||
console.log(`${'='.repeat(80)}\n`);
|
||
});
|
||
}
|
||
|
||
// Arrêt propre
|
||
process.on('SIGINT', async () => {
|
||
console.log('\n⏹️ Arrêt du serveur...');
|
||
if (pool) {
|
||
await pool.close();
|
||
console.log('✅ Connexion fermée');
|
||
}
|
||
process.exit(0);
|
||
});
|
||
|
||
startServer(); |