feat: add check backup
This commit is contained in:
149
server/api/check-backup.get.ts
Normal file
149
server/api/check-backup.get.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
runSsh,
|
||||
shellQuote,
|
||||
resolveFolderRemoteDir
|
||||
} from "../utils/ssh.ts"
|
||||
|
||||
import backupOptions from "../config/backup-options.json"
|
||||
|
||||
type BackupTarget = {
|
||||
name: string
|
||||
}
|
||||
|
||||
type LatestBackupInfo = {
|
||||
fileName: string | null
|
||||
modifiedAt: string | null
|
||||
}
|
||||
|
||||
const BACKUP_HOUR = 19
|
||||
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<LatestBackupInfo> {
|
||||
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: error instanceof Error ? error.message : String(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
return { results }
|
||||
})
|
||||
Reference in New Issue
Block a user