feat: phase A — backup B2, healthcheck /api/status, vector tiles Tippecanoe

- A1: scripts/backup-postgres.sh — pg_dump quotidien compressé → Backblaze B2 (rclone), rétention 30j
- A2: StatusController retourne HTTP 503 + healthy/alertReason si dernière observation > 6h
- A4: Dockerfile installe tippecanoe, GenerateTilesCommand génère 5 tilesets (obs + h6/12/24/48), Caddyfile sert /tiles/* sans fallback SPA, SargassesMap.jsx passe en sources vector tiles statiques
- chore: backend/public/assets/ ajouté au .gitignore (build artifacts)
- chore: setup vitest frontend + ImpactScoreServiceTest (session précédente)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Gwadaking
2026-04-10 03:40:32 -04:00
parent 1d8771cdcb
commit 9712cb54db
20 changed files with 1653 additions and 872 deletions

View File

@@ -29,7 +29,27 @@ class StatusController extends AbstractController
ORDER BY requested_at DESC LIMIT 1"
);
// Healthcheck : données fraîches si dernière observation < 6h
$healthy = false;
$alertReason = null;
if ($obs !== false && $obs['detected_at'] !== null) {
$detectedAt = new \DateTimeImmutable($obs['detected_at'], new \DateTimeZone('UTC'));
$now = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
$ageSeconds = $now->getTimestamp() - $detectedAt->getTimestamp();
if ($ageSeconds <= 6 * 3600) {
$healthy = true;
} else {
$alertReason = 'stale_data';
}
} else {
$alertReason = 'no_data';
}
return $this->json([
'healthy' => $healthy,
'alertReason' => $alertReason,
'lastObservation' => $obs !== false ? [
'detectedAt' => $obs['detected_at'],
'source' => $obs['source'],
@@ -43,6 +63,6 @@ class StatusController extends AbstractController
'finishedAt' => $job['processed_at'],
'retryCount' => (int) $job['retry_count'],
] : null,
]);
], $healthy ? 200 : 503);
}
}