feat : DATA_PATH configurable, DataGit auto-commit, UpdateChecker branche (v1.4.0)

- DATA_PATH : chemin /data hors document root, configurable via .env
  (fallback sur BASE_PATH/data si absent)
- DataGit : auto-commit git sur toutes les écritures articles/livres
  (create, update, delete, meta, tags, fichiers, liens…) sauf autosave
- UpdateChecker : getBranch() / getLastChecked() / clearCache(),
  branche configurable via FOLIO_UPDATE_BRANCH (plus de main hardcodé)
- Admin dashboard : affiche la branche suivie, date du dernier contrôle,
  bouton Vérifier pour forcer le check sans attendre le TTL
- CLAUDE.md : architecture DATA_PATH et flux de déploiement documentés

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 09:17:55 +02:00
parent 55a2120be1
commit 16965ee8cb
18 changed files with 236 additions and 37 deletions
+25 -13
View File
@@ -24,12 +24,14 @@ require_once BASE_PATH . '/src/auth.php';
require_once BASE_PATH . '/src/SiteSettings.php';
require_once BASE_PATH . '/src/ArticleManager.php';
require_once BASE_PATH . '/src/BookManager.php';
require_once BASE_PATH . '/src/DataGit.php';
$articles = new ArticleManager(BASE_PATH . '/data');
$books = new BookManager(BASE_PATH . '/data/books');
$_dataGit = new DataGit(DATA_PATH);
$articles = new ArticleManager(DATA_PATH, $_dataGit);
$books = new BookManager(DATA_PATH . '/books', $_dataGit);
// ─── Mode maintenance ──────────────────────────────────────────────────────
if (file_exists(BASE_PATH . '/data/.maintenance')) {
if (file_exists(DATA_PATH . '/.maintenance')) {
http_response_code(503);
header('Retry-After: 60');
include BASE_PATH . '/templates/maintenance.php';
@@ -37,7 +39,7 @@ if (file_exists(BASE_PATH . '/data/.maintenance')) {
}
require_once BASE_PATH . '/src/UpdateChecker.php';
$_updateChecker = new UpdateChecker(BASE_PATH . '/data', BASE_PATH);
$_updateChecker = new UpdateChecker(DATA_PATH, BASE_PATH);
$action = $_GET['action'] ?? 'list';
$uuid = $_GET['uuid'] ?? '';
@@ -78,7 +80,7 @@ function searchAndRedirect(string $rawPath, ArticleManager $articles): void
// ─── Pages statiques depuis data/site/ ──────────────────────────────────────
function loadSitePageData(string $slug): array
{
$base = BASE_PATH . '/data/site';
$base = DATA_PATH . '/site';
$meta = [];
$raw = @file_get_contents($base . '/' . $slug . '.json');
if ($raw !== false) {
@@ -1383,7 +1385,7 @@ switch ($action) {
case 'flux':
require_once BASE_PATH . '/src/FeedFetcher.php';
$fetcher = new FeedFetcher(BASE_PATH . '/data/_cache/feeds');
$fetcher = new FeedFetcher(DATA_PATH . '/_cache/feeds');
$fluxItems = [];
$pdo = dbPdo();
if ($pdo) {
@@ -1535,8 +1537,8 @@ switch ($action) {
echo json_encode(['ok' => false, 'error' => 'Paramètres invalides']);
exit;
}
$cfSrc = BASE_PATH . '/data/' . $cfFrom . '/files/' . $cfName;
$cfDstDir = BASE_PATH . '/data/' . $cfTo . '/files';
$cfSrc = DATA_PATH . '/' . $cfFrom . '/files/' . $cfName;
$cfDstDir = DATA_PATH . '/' . $cfTo . '/files';
$cfDst = $cfDstDir . '/' . $cfName;
if (!file_exists($cfSrc)) {
echo json_encode(['ok' => false, 'error' => 'Fichier source introuvable']);
@@ -1649,7 +1651,7 @@ switch ($action) {
// Capture d'écran pour prévisualisation (pages HTML uniquement, URL externes uniquement)
$step2Screenshot = null;
if (!$step2IsInternal && str_starts_with($step2Meta['mime'] ?? '', 'text/html')) {
$filesDir = BASE_PATH . '/data/' . $uuid . '/files';
$filesDir = DATA_PATH . '/' . $uuid . '/files';
if (!is_dir($filesDir)) {
mkdir($filesDir, 0755, true);
}
@@ -1725,7 +1727,7 @@ switch ($action) {
header('Location: /import/' . rawurlencode($urlUuid) . '?error=1');
exit;
}
$filesDir = BASE_PATH . '/data/' . $urlUuid . '/files';
$filesDir = DATA_PATH . '/' . $urlUuid . '/files';
$previewPath = $filesDir . '/' . $screenshotFile;
if (!file_exists($previewPath)) {
header('Location: /import/' . rawurlencode($urlUuid) . '?error=1');
@@ -1744,7 +1746,7 @@ switch ($action) {
}
if ($mode === 'link') {
$filesDir = BASE_PATH . '/data/' . $urlUuid . '/files';
$filesDir = DATA_PATH . '/' . $urlUuid . '/files';
if (!is_dir($filesDir)) {
mkdir($filesDir, 0755, true);
}
@@ -1895,7 +1897,7 @@ switch ($action) {
$done = $fail = $skip = 0;
foreach ($articles->getAll() as $article) {
$artUuid = $article['uuid'];
$filesDir = BASE_PATH . '/data/' . $artUuid . '/files';
$filesDir = DATA_PATH . '/' . $artUuid . '/files';
foreach ($article['external_links'] ?? [] as $link) {
$lMeta = $link['meta'] ?? [];
$lMime = $lMeta['mime'] ?? 'text/html';
@@ -2784,7 +2786,7 @@ switch ($action) {
http_response_code(403);
exit;
}
$_cmDataDir = BASE_PATH . '/data';
$_cmDataDir = DATA_PATH;
$_cmTrack = $_cmDataDir . '/.content_migrations.json';
$_cmFlag = $_cmDataDir . '/.maintenance';
$_cmApplied = file_exists($_cmTrack) ? (json_decode((string) file_get_contents($_cmTrack), true) ?? []) : [];
@@ -2814,6 +2816,16 @@ switch ($action) {
header('Location: /admin?tab=dashboard&notice=' . ($_cmErrors ? 'migration_error' : 'migrated'));
exit;
case 'force_update_check':
requireAuth();
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(403);
exit;
}
$_updateChecker->clearCache();
header('Location: /admin?tab=dashboard');
exit;
case 'admin_save_site':
requireAuth();
if (!isAdmin() || $_SERVER['REQUEST_METHOD'] !== 'POST') {