import { runSsh, shellQuote, resolveFolderRemoteDir } from "../utils/ssh.ts" import backupOptions from "../config/backup-options.json" export const BACKUP_HOUR = Number(process.env.BACKUPS_HOUR) || 19 type BackupTarget = { name: string } type LatestBackupInfo = { fileName: string | null modifiedAt: string | null } const backupTargets = backupOptions as BackupTarget[] function toLabel(name: string) { if (name === "sirh") return "SIRH" return name.charAt(0).toUpperCase() + name.slice(1) } function pad(value: number) { return String(value).padStart(2, "0") } function formatDateKey(date: Date) { return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}` } function getExpectedBackupDate(now: Date) { const expected = new Date(now) if (now.getHours() < BACKUP_HOUR) { expected.setDate(expected.getDate() - 1) } expected.setHours(BACKUP_HOUR, 0, 0, 0) return expected } function extractBackupDate(fileName: string | null) { if (!fileName) return null const normalized = fileName.replace(/[^0-9]/g, "") const yearFirst = normalized.match(/(20\d{2})(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])/) if (yearFirst) { return `${yearFirst[1]}-${yearFirst[2]}-${yearFirst[3]}` } const dayFirst = normalized.match(/(0[1-9]|[12]\d|3[01])(0[1-9]|1[0-2])(20\d{2})/) if (dayFirst) { return `${dayFirst[3]}-${dayFirst[2]}-${dayFirst[1]}` } return null } function parseRemoteTimestamp(value: string) { const timestamp = Number(value) if (!Number.isFinite(timestamp) || timestamp <= 0) { return null } return new Date(timestamp * 1000).toISOString() } async function getLatestBackupInfo(remoteDir: string): Promise { const output = await runSsh( `cd ${shellQuote(remoteDir)} && for file in *; do [ -e "$file" ] || continue; printf '%s\\t%s\\n' "$(stat -c '%Y' "$file")" "$file"; done | sort -rn | head -n 1` ) const line = output.trim() if (!line) { return { fileName: null, modifiedAt: null } } const [timestamp, ...nameParts] = line.split("\t") const fileName = nameParts.join("\t").trim() || null return { fileName, modifiedAt: parseRemoteTimestamp(timestamp) } } export default defineEventHandler(async () => { const now = new Date() const expectedBackupDate = getExpectedBackupDate(now) const expectedDateKey = formatDateKey(expectedBackupDate) const checkedAt = now.toISOString() const results = await Promise.all( backupTargets.map(async (target) => { try { const remoteDir = await resolveFolderRemoteDir(target.name) if (!remoteDir) { return { label: toLabel(target.name), folder: target.name, ok: false, status: 0, checkedAt, latestBackup: null, latestBackupAt: null, backupDate: null, expectedBackupDate: expectedDateKey, error: "Dossier de backup introuvable" } } const latestBackupInfo = await getLatestBackupInfo(remoteDir) const backupDate = extractBackupDate(latestBackupInfo.fileName) const ok = backupDate === expectedDateKey return { label: toLabel(target.name), folder: target.name, ok, status: ok ? 200 : 0, checkedAt, latestBackup: latestBackupInfo.fileName, latestBackupAt: latestBackupInfo.modifiedAt, backupDate, expectedBackupDate: expectedDateKey, error: latestBackupInfo.fileName ? undefined : "Aucun backup trouve" } } catch (error) { return { label: toLabel(target.name), folder: target.name, ok: false, status: 0, checkedAt, latestBackup: null, latestBackupAt: null, backupDate: null, expectedBackupDate: expectedDateKey, error: "Erreur lors de la verification" } } }) ) return { results } })