Files
GTF_VersionFinal/GTF_GIT/GTF/project/backend/config/server.js
T
2026-06-26 14:50:55 +02:00

849 lines
30 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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();