Major refactor

- update the structure to have monolithic setup
- updated deployments
- added live-reloading for devops
This commit is contained in:
Théophile Gervreau-Mercier
2026-02-05 20:07:05 +01:00
parent f23fbb481b
commit d2b3c6ca67
75 changed files with 3359 additions and 3987 deletions

366
admin/import.php Normal file
View File

@@ -0,0 +1,366 @@
<?php
// CSV Import page for Post-ERG thesis database
// This page allows importing thesis data from CSV files
session_start();
// Generate CSRF token
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
require_once __DIR__ . '/../lib/Database.php';
$message = '';
$errors = [];
$importedCount = 0;
$skippedCount = 0;
$importResults = [];
// Handle CSV upload and import
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['csv_file'])) {
// Verify CSRF token
if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
$errors[] = "Erreur de sécurité : token invalide.";
} else {
try {
$db = new Database();
$pdo = $db->getPDO();
// Check file upload
if ($_FILES['csv_file']['error'] !== UPLOAD_ERR_OK) {
throw new Exception("Erreur lors du téléversement du fichier.");
}
// Read CSV file
$csvFile = $_FILES['csv_file']['tmp_name'];
$handle = fopen($csvFile, 'r');
if (!$handle) {
throw new Exception("Impossible d'ouvrir le fichier CSV.");
}
// Skip first two rows (empty and headers)
fgetcsv($handle); // Empty row
$headers = fgetcsv($handle); // Header row
fgetcsv($handle); // Description row
$headers = fgetcsv($handle); // Actual column names
// Map CSV columns
$columnMap = [
0 => 'identifier', // Identifiant
1 => 'title', // Titre
2 => 'subtitle', // Sous-titre
3 => 'authors', // Auteur·ice(s)
4 => 'contact', // Contact
5 => 'supervisors', // Promoteur·ice(s)
6 => 'formats', // Format
7 => 'year', // Année
8 => 'ap', // AP
9 => 'orientation', // Orientation
10 => 'finality', // Finalité
11 => 'keywords', // Mots-clés
12 => 'synopsis', // Synopsis
13 => 'context', // Contexte
14 => 'remarks', // Remarques
15 => 'language', // Langue
16 => 'access', // Autorisation
17 => 'license', // License
18 => 'size_info', // taille
19 => 'jury_points', // Points sur 20
20 => 'baiu_link', // lien BAIU
];
// Orientation abbreviation mapping
$orientationMap = [
'SC' => 'Sculpture',
'VI' => 'Vidéographie',
'CA' => 'Cinéma d\'animation',
'IP' => 'Installation-Performance',
'PE' => 'Peinture',
'PH' => 'Photographie',
'DE' => 'Dessin',
'AN' => 'Arts Numériques',
'GR' => 'Graphisme',
'TY' => 'Typographie',
'DN' => 'Design Numérique',
'IL' => 'Illustration',
'BD' => 'Bande-Dessinée',
'SE' => 'Sérigraphie',
'GV' => 'Gravure',
];
// Process each row
$lineNumber = 5; // Start after headers
while (($row = fgetcsv($handle)) !== false) {
$lineNumber++;
// Skip empty rows
if (empty($row[0]) && empty($row[1])) {
continue;
}
try {
$db->beginTransaction();
// Extract data
$identifier = trim($row[0] ?? '');
$title = trim($row[1] ?? '');
$subtitle = trim($row[2] ?? '');
$authorsRaw = trim($row[3] ?? '');
$contact = trim($row[4] ?? '');
$supervisorsRaw = trim($row[5] ?? '');
$formatsRaw = trim($row[6] ?? '');
$year = intval($row[7] ?? 0);
$apCode = trim($row[8] ?? '');
$orientationCode = trim($row[9] ?? '');
$finalityName = trim($row[10] ?? '');
$keywordsRaw = trim($row[11] ?? '');
$synopsis = trim($row[12] ?? '');
$context = trim($row[13] ?? '');
$remarks = trim($row[14] ?? '');
$languageRaw = trim($row[15] ?? '');
$access = trim($row[16] ?? '');
$license = trim($row[17] ?? '');
$sizeInfo = trim($row[18] ?? '');
$juryPoints = !empty($row[19]) ? floatval($row[19]) : null;
$baiuLink = trim($row[20] ?? '');
// Validate required fields
if (empty($title) || empty($year)) {
throw new Exception("Ligne $lineNumber: Titre et année requis.");
}
// Map orientation
$orientationName = isset($orientationMap[$orientationCode]) ? $orientationMap[$orientationCode] : null;
$orientationId = null;
if ($orientationName) {
$orientationId = $db->getOrientationId($orientationName);
}
// Map AP program
$apProgramId = null;
if (!empty($apCode)) {
$stmt = $pdo->prepare("SELECT id FROM ap_programs WHERE code = ?");
$stmt->execute([$apCode]);
$result = $stmt->fetch();
if ($result) {
$apProgramId = $result['id'];
}
}
// Map finality
$finalityId = null;
if (!empty($finalityName)) {
$finalityId = $db->getFinalityId($finalityName);
}
// Insert thesis
$stmt = $pdo->prepare("
INSERT INTO theses (
identifier, title, subtitle, year,
orientation_id, ap_program_id, finality_id,
synopsis, context_note, remarks,
file_size_info, jury_points, baiu_link,
submitted_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
");
$stmt->execute([
!empty($identifier) ? $identifier : null,
$title,
!empty($subtitle) ? $subtitle : null,
$year,
$orientationId,
$apProgramId,
$finalityId,
!empty($synopsis) ? $synopsis : null,
!empty($context) ? $context : null,
!empty($remarks) ? $remarks : null,
!empty($sizeInfo) ? $sizeInfo : null,
$juryPoints,
!empty($baiuLink) ? $baiuLink : null
]);
$thesisId = $pdo->lastInsertId();
// Add authors
if (!empty($authorsRaw)) {
$authors = array_map('trim', explode(',', $authorsRaw));
foreach ($authors as $index => $authorName) {
if (!empty($authorName)) {
$authorId = $db->findOrCreateAuthor($authorName, $index === 0 ? $contact : null);
$stmt = $pdo->prepare("INSERT INTO thesis_authors (thesis_id, author_id, author_order) VALUES (?, ?, ?)");
$stmt->execute([$thesisId, $authorId, $index + 1]);
}
}
}
// Add supervisors
if (!empty($supervisorsRaw)) {
$supervisors = array_map('trim', explode(',', $supervisorsRaw));
foreach ($supervisors as $index => $supervisorName) {
if (!empty($supervisorName)) {
$supervisorId = $db->findOrCreateSupervisor($supervisorName);
$stmt = $pdo->prepare("INSERT INTO thesis_supervisors (thesis_id, supervisor_id, supervisor_order) VALUES (?, ?, ?)");
$stmt->execute([$thesisId, $supervisorId, $index + 1]);
}
}
}
// Add keywords
if (!empty($keywordsRaw)) {
$keywords = array_map('trim', explode(',', $keywordsRaw));
$keywords = array_slice($keywords, 0, 10); // Max 10
foreach ($keywords as $keyword) {
if (!empty($keyword)) {
$keywordId = $db->findOrCreateKeyword($keyword);
if ($keywordId) {
$stmt = $pdo->prepare("INSERT INTO thesis_keywords (thesis_id, keyword_id) VALUES (?, ?)");
$stmt->execute([$thesisId, $keywordId]);
}
}
}
}
// Add language
if (!empty($languageRaw)) {
$languageId = $db->getLanguageId(ucfirst(strtolower($languageRaw)));
if ($languageId) {
$stmt = $pdo->prepare("INSERT INTO thesis_languages (thesis_id, language_id) VALUES (?, ?)");
$stmt->execute([$thesisId, $languageId]);
}
}
// Add formats
if (!empty($formatsRaw)) {
$formats = array_map('trim', explode(',', $formatsRaw));
foreach ($formats as $formatName) {
if (!empty($formatName)) {
$formatId = $db->getFormatId(ucfirst(strtolower($formatName)));
if ($formatId) {
$stmt = $pdo->prepare("INSERT INTO thesis_formats (thesis_id, format_id) VALUES (?, ?)");
$stmt->execute([$thesisId, $formatId]);
}
}
}
}
$db->commit();
$importedCount++;
$importResults[] = "✓ Ligne $lineNumber: \"$title\" importé (ID: $thesisId)";
} catch (Exception $e) {
$db->rollback();
$skippedCount++;
$importResults[] = " Ligne $lineNumber: " . $e->getMessage();
error_log("Import error on line $lineNumber: " . $e->getMessage());
}
}
fclose($handle);
$message = "Import terminé : $importedCount TFE importés, $skippedCount ignorés.";
} catch (Exception $e) {
$errors[] = $e->getMessage();
error_log("CSV import error: " . $e->getMessage());
}
}
// Regenerate CSRF token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Import CSV - Post-ERG</title>
<link rel="stylesheet" href="assets/normalize.css">
<link rel="stylesheet" href="https://raw.githack.com/waldyrious/downstyler/master/downstyler.css" />
<link rel="shortcut icon" href="assets/icon.svg" type="image/svg">
</head>
<body>
<header>
<h1>Import CSV - Post-ERG</h1>
<nav>
<a href="index.php">← Nouveau TFE</a> |
<a href="list.php">📋 Liste des TFE</a>
</nav>
</header>
<main>
<h2>Importer des TFE depuis un fichier CSV</h2>
<?php if (!empty($errors)): ?>
<div style="background: #fee; border: 2px solid #c00; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #c00;">
<strong>⚠️ Erreurs:</strong>
<ul>
<?php foreach ($errors as $error): ?>
<li><?php echo htmlspecialchars($error); ?></li>
<?php endforeach; ?>
</ul>
</div>
<?php endif; ?>
<?php if ($message): ?>
<div style="background: #efe; border: 2px solid #0a0; padding: 1rem; margin-bottom: 1rem; border-radius: 4px; color: #0a0;">
<strong>✓ <?php echo htmlspecialchars($message); ?></strong>
</div>
<?php endif; ?>
<form action="import.php" method="post" enctype="multipart/form-data">
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<fieldset>
<legend>Sélectionner un fichier CSV</legend>
<p><strong>Format attendu:</strong></p>
<ul>
<li>Colonnes: Identifiant, Titre, Sous-titre, Auteur·ice(s), Contact, Promoteur·ice(s), Format, Année, AP, Orientation, Finalité, Mots-clés, Synopsis, Contexte, Remarques, Langue, Autorisation, License, taille, Points sur 20, lien BAIU</li>
<li>Les deux premières lignes seront ignorées (entête)</li>
<li>Séparateur: virgule</li>
<li>Encodage: UTF-8</li>
</ul>
<label for="csv_file">Fichier CSV:</label>
<input type="file" id="csv_file" name="csv_file" accept=".csv" required>
<button type="submit">Importer</button>
</fieldset>
</form>
<?php if (!empty($importResults)): ?>
<h3>Résultats de l'import</h3>
<div style="background: #f5f5f5; padding: 1rem; border-radius: 4px; max-height: 400px; overflow-y: auto;">
<pre style="margin: 0; font-size: 0.9em;"><?php
foreach ($importResults as $result) {
echo htmlspecialchars($result) . "\n";
}
?></pre>
</div>
<?php endif; ?>
<hr>
<h3>Notes importantes</h3>
<ul>
<li><strong>Codes orientation:</strong> SC (Sculpture), VI (Vidéographie), CA (Cinéma d'animation), IP (Installation-Performance), etc.</li>
<li><strong>Codes AP:</strong> DPM, LIENS, APS (comme dans la base)</li>
<li><strong>Auteurs multiples:</strong> Séparer par des virgules</li>
<li><strong>Mots-clés:</strong> Maximum 10, séparés par des virgules</li>
<li><strong>Formats:</strong> Séparer par des virgules</li>
<li>Les lignes avec erreurs seront ignorées et loggées</li>
</ul>
<h3>Exemple de fichier CSV</h3>
<p>Voir: <code>../db/Database_TFE_test.csv</code></p>
</main>
<footer>
<p>Import CSV - Post-ERG Database</p>
</footer>
</body>
</html>