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

396 lines
13 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const sql = require('mssql');
require('dotenv').config();
const app = express();
const PORT = 3000;
// Middleware
app.use(express.json());
app.use(cors({
origin: [
'http://localhost:3001',
'http://localhost:8000',
'http://127.0.0.1:3001',
'https://mygtf.ensup-adm.net:3001',
'https://mygtf.ensup-adm.net'
],
credentials: true
}));
// Configuration SQL
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 }
};
let pool = null;
let systemStatus = {
hasFormateurEmailColumn: false,
hasFormateurView: false,
canAccessFormateurView: false,
hasFormateurLocal: false,
operatingMode: 'unknown'
};
// Connexion DB
async function connectDatabase() {
try {
pool = await sql.connect(dbConfig);
console.log('Base de données connectée');
await checkSystemStatus();
return true;
} catch (error) {
console.error('Erreur de connexion DB:', error.message);
return false;
}
}
// Diagnostic du système
async function checkSystemStatus() {
try {
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;
const viewCheck = await pool.request().query(`
SELECT COUNT(*) AS count
FROM INFORMATION_SCHEMA.VIEWS
WHERE TABLE_NAME = 'v_Formateurs_CD_Declarations'
`);
systemStatus.hasFormateurView = viewCheck.recordset[0].count > 0;
if (systemStatus.hasFormateurView) {
try {
await pool.request().query(`SELECT TOP 1 userPrincipalName FROM dbo.v_Formateurs_CD_Declarations`);
systemStatus.canAccessFormateurView = true;
} catch {
systemStatus.canAccessFormateurView = false;
}
}
systemStatus.operatingMode = systemStatus.hasFormateurEmailColumn && systemStatus.canAccessFormateurView
? 'new_with_view'
: 'legacy_hash';
console.log('État système:', systemStatus);
} catch (error) {
console.error('Erreur diagnostic:', error.message);
systemStatus.operatingMode = 'legacy_hash';
}
}
// Hash legacy
function generateHashFromEmail(email) {
let hash = 0;
for (let i = 0; i < email.length; i++) {
const char = email.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return Math.abs(hash) % 10000 + 1000;
}
// Conversion heure
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`;
};
// ROUTES
// Diagnostic complet
app.get('/api/diagnostic', async (req, res) => {
await checkSystemStatus();
res.json(systemStatus);
});
// Liste formateurs avec déclaration jointe
// Route /api/formateurs modifiée pour utiliser formateurs_local
app.get('/api/formateurs', async (req, res) => {
try {
const { email, search } = req.query;
let query = `
SELECT
userPrincipalName as email,
CONCAT(givenname, ' ', surname) as nom_complet,
Campus,
Contrat
FROM formateurs_local
`;
const request = pool.request();
if (email) {
query += ` WHERE userPrincipalName = @email`;
request.input('email', sql.NVarChar, email);
} else if (search) {
query += ` WHERE CONCAT(givenname, ' ', surname) LIKE @search OR userPrincipalName LIKE @search`;
request.input('search', sql.NVarChar, `%${search}%`);
}
query += ` ORDER BY surname, givenname`;
const result = await request.query(query);
res.json({
success: true,
data: result.recordset
});
} catch (error) {
console.error('Erreur /api/formateurs:', error);
res.status(500).json({
success: false,
error: error.message
});
}
});
app.post('/api/save_declaration', async (req, res) => {
try {
const { id, date, activityType, hours, description, user, startTime, endTime } = req.body;
if (!user?.email) 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 dans la base.'
});
}
const typeDemandeId = typeResult.recordset[0].id;
// Vérifier que le formateur existe dans formateurs_local
const formateur = await pool.request()
.input('email', sql.NVarChar, user.email)
.query('SELECT TOP 1 userPrincipalName FROM formateurs_local WHERE userPrincipalName=@email');
if (formateur.recordset.length === 0) {
return res.status(403).json({ error: 'Formateur non trouvé dans la table formateurs_local.' });
}
const formateurEmail = formateur.recordset[0].userPrincipalName;
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 {
// NOUVELLE SAISIE
console.log('Création nouvelle déclaration pour', formateurEmail, 'le', date);
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('✅ Nouvelle déclaration créée');
}
res.json({ success: true, message: 'Déclaration sauvegardée' });
} catch (error) {
console.error("Erreur save_declaration:", error);
res.status(500).json({ error: error.message, stack: error.stack });
}
});
// Récupérer déclarations par email
app.get('/api/get_declarations_by_email', async (req, res) => {
try {
const { email } = req.query;
if (!email) return res.status(400).json({ error: 'Email requis' });
let query = `
SELECT
d.id,
d.formateur_email_fk AS formateur_email,
CONCAT(f.givenname, ' ', f.surname) 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);
res.json(result.recordset);
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
});
// Récupérer toutes les déclarations
app.get('/api/get_declarations', async (req, res) => {
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,
CONCAT(f.givenname, ' ', f.surname) 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);
res.json(result.recordset);
} catch (error) {
console.error(error);
res.status(500).json({ error: error.message });
}
});
// Supprimer une déclaration
app.delete('/api/delete_declaration/:id', async (req, res) => {
try {
const { id } = req.params;
if (!id) return res.status(400).json({ error: 'ID de la déclaration requis' });
// Vérifier si la déclaration existe
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' });
}
// Supprimer la déclaration
await pool.request()
.input('id', sql.Int, id)
.query('DELETE FROM declarations WHERE id = @id');
res.json({ success: true, message: 'Déclaration supprimée' });
} catch (error) {
console.error('Erreur delete_declaration:', error);
res.status(500).json({ error: error.message });
}
});
// ==================== ROUTES GESTION DES CLÔTURES ====================
// Vérifier si une date est clôturée
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); // '2025-01-15' -> '2025-01'
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 });
}
});
// Récupérer toutes les 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 serveur
async function startServer() {
const dbConnected = await connectDatabase();
if (!dbConnected) return console.log('Impossible de démarrer sans DB');
app.listen(PORT, () => {
console.log(`🚀 Serveur démarré sur http://localhost:${PORT}`);
console.log('Mode fonctionnement:', systemStatus.operatingMode);
});
}
process.on('SIGINT', async () => {
if (pool) await pool.close();
process.exit(0);
});
startServer();