Add comprehensive thesis management system with database migration

This commit introduces a complete thesis management interface and migrates
the system from YAML-based storage to SQLite:

Core Changes:
- Add Database.php helper class with PDO connection and entity management
- Add list.php for viewing all theses with filtering and sorting
- Add edit.php for modifying existing thesis records
- Add import.php for migrating legacy YAML data to SQLite
- Add justfile with development tasks (serve, init-test-db, etc.)

Documentation:
- Add MIGRATION.md with complete migration guide and architecture docs
- Update README.md with database setup and Just recipe instructions
- Update .gitignore to exclude test databases and error logs

Modified Forms:
- Enhanced formulaire.php with transaction-based SQLite processing
- Updated index.php with database-driven form options
- Improved thanks.php to read from database views

The new architecture provides:
- Normalized database schema (19 tables, 2 views)
- Transaction safety and referential integrity
- CRUD operations for thesis management
- Filtering by year, orientation, AP program, publication status
- Secure file handling with metadata tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Théophile Gervreau-Mercier
2026-01-27 15:43:01 +01:00
parent 99ccd60f90
commit 95f52d549e
22 changed files with 3263 additions and 725 deletions

View File

@@ -3,61 +3,73 @@ ini_set('display_errors', 0);
ini_set('log_errors', 1);
ini_set('error_log', 'error.log');
require_once 'vendor/autoload.php';
use Symfony\Component\Yaml\Yaml;
require_once 'Database.php';
$page = isset($_GET['page']) ? intval($_GET['page']) : 1;
$itemsPerPage = 10;
$dir = "data/yaml/*.yaml";
$yamlFiles = glob($dir);
$data = [];
foreach ($yamlFiles as $yamlFile) {
$data[] = Yaml::parseFile($yamlFile);
try {
$db = Database::getInstance();
$offset = ($page - 1) * $itemsPerPage;
$itemsToLoad = $db->getPublishedTheses($itemsPerPage, $offset);
$totalItems = $db->countPublishedTheses();
$totalPages = ceil($totalItems / $itemsPerPage);
} catch (Exception $e) {
error_log("Error loading theses: " . $e->getMessage());
$itemsToLoad = [];
$totalPages = 0;
}
usort($data, function ($a, $b) {
return $a['année'] <=> $b['année'];
});
$offset = ($page - 1) * $itemsPerPage;
$itemsToLoad = array_slice($data, $offset, $itemsPerPage);
include 'inc/header.php'; ?>
<section class="section">
<div class="container">
<div class="columns is-multiline">
<?php foreach ($itemsToLoad as $key => $item): ?>
<?php foreach ($itemsToLoad as $item): ?>
<div class="column is-one-fifth">
<a href="memoire.php?file=<?= urlencode($yamlFiles[$key]); ?>" class="card-link">
<a href="memoire.php?id=<?= $item['id']; ?>" class="card-link">
<div class="card">
<?php if (isset($item['couverture'])): ?>
<?php
// Get cover image from thesis_files if available
$coverImage = null;
if (!empty($item['id'])) {
$files = $db->getThesisFiles($item['id']);
foreach ($files as $file) {
$ext = strtolower(pathinfo($file['file_path'], PATHINFO_EXTENSION));
if (in_array($ext, ['jpg', 'jpeg', 'png', 'gif']) && $file['file_type'] === 'main') {
$coverImage = $file['file_path'];
break;
}
}
}
?>
<?php if ($coverImage): ?>
<div class="card-image">
<figure class="image ">
<img src="<?= $item['couverture']; ?>" alt="Image preview">
<img src="<?= htmlspecialchars($coverImage); ?>" alt="Image preview">
</figure>
</div>
<?php endif; ?>
<div class="card-content">
<h4 class="title is-4">
<?= $item['titre']; ?>
<?= htmlspecialchars($item['title']); ?>
</h4>
<h2 class="subtitle">
<?= $item['auteurice']; ?>
</p>
<?= htmlspecialchars($item['authors'] ?? 'Auteur inconnu'); ?>
</h2>
<h3 class="tag title is-6 is-link is-light">
<?= $item['année']; ?>
</h3>
<?= htmlspecialchars($item['year']); ?>
</h3>
<p class="block content">
<?php
$excerpt_length = 150;
$description_excerpt = substr($item['description'], 0, $excerpt_length) . '...';
$synopsis = $item['synopsis'] ?? '';
$description_excerpt = strlen($synopsis) > $excerpt_length
? substr($synopsis, 0, $excerpt_length) . '...'
: $synopsis;
?>
<?= $description_excerpt; ?>
<?= htmlspecialchars($description_excerpt); ?>
</p>
</div>