mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add timezone support for backup file naming and extraction
This commit is contained in:
@@ -192,6 +192,7 @@ async function executeConfiguredBackup(
|
|||||||
});
|
});
|
||||||
const archive = await buildBackupArchive(env, now, {
|
const archive = await buildBackupArchive(env, now, {
|
||||||
includeAttachments: destination.includeAttachments,
|
includeAttachments: destination.includeAttachments,
|
||||||
|
timeZone: destination.schedule.timezone,
|
||||||
progress: progress
|
progress: progress
|
||||||
? async (event) => {
|
? async (event) => {
|
||||||
if (event.step === 'archive_ready') {
|
if (event.step === 'archive_ready') {
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export interface BackupFileIntegrityCheckResult {
|
|||||||
export interface BuildBackupArchiveOptions {
|
export interface BuildBackupArchiveOptions {
|
||||||
includeAttachments?: boolean;
|
includeAttachments?: boolean;
|
||||||
progress?: BackupArchiveBuildProgressReporter;
|
progress?: BackupArchiveBuildProgressReporter;
|
||||||
|
timeZone?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackupArchiveBuildProgressEvent {
|
export interface BackupArchiveBuildProgressEvent {
|
||||||
@@ -93,17 +94,30 @@ async function sha256Hex(bytes: Uint8Array): Promise<string> {
|
|||||||
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildBackupFileName(date: Date = new Date(), checksumPrefix: string | null = null): string {
|
function getDateParts(date: Date, timeZone: string): string {
|
||||||
const parts = [
|
const formatter = new Intl.DateTimeFormat('en-CA', {
|
||||||
date.getUTCFullYear().toString().padStart(4, '0'),
|
timeZone,
|
||||||
(date.getUTCMonth() + 1).toString().padStart(2, '0'),
|
year: 'numeric',
|
||||||
date.getUTCDate().toString().padStart(2, '0'),
|
month: '2-digit',
|
||||||
date.getUTCHours().toString().padStart(2, '0'),
|
day: '2-digit',
|
||||||
date.getUTCMinutes().toString().padStart(2, '0'),
|
hour: '2-digit',
|
||||||
date.getUTCSeconds().toString().padStart(2, '0'),
|
minute: '2-digit',
|
||||||
];
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const pick = (type: string): string => parts.find((part) => part.type === type)?.value || '';
|
||||||
|
return `${pick('year')}${pick('month')}${pick('day')}_${pick('hour')}${pick('minute')}${pick('second')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBackupFileNameInTimeZone(
|
||||||
|
date: Date = new Date(),
|
||||||
|
checksumPrefix: string | null = null,
|
||||||
|
timeZone: string = 'UTC'
|
||||||
|
): string {
|
||||||
|
const parts = getDateParts(date, timeZone);
|
||||||
const suffix = checksumPrefix ? `_${checksumPrefix}` : '';
|
const suffix = checksumPrefix ? `_${checksumPrefix}` : '';
|
||||||
return `nodewarden_backup_${parts[0]}${parts[1]}${parts[2]}_${parts[3]}${parts[4]}${parts[5]}${suffix}.zip`;
|
return `nodewarden_backup_${parts}${suffix}.zip`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractBackupFileChecksumPrefix(fileName: string): string | null {
|
export function extractBackupFileChecksumPrefix(fileName: string): string | null {
|
||||||
@@ -398,7 +412,8 @@ export async function buildBackupArchive(
|
|||||||
});
|
});
|
||||||
const bytes = zipSync(createZipEntries(files));
|
const bytes = zipSync(createZipEntries(files));
|
||||||
const fileHashPrefix = (await sha256Hex(bytes)).slice(0, BACKUP_FILE_HASH_PREFIX_LENGTH);
|
const fileHashPrefix = (await sha256Hex(bytes)).slice(0, BACKUP_FILE_HASH_PREFIX_LENGTH);
|
||||||
const fileName = buildBackupFileName(date, fileHashPrefix);
|
const backupTimeZone = options.timeZone || 'UTC';
|
||||||
|
const fileName = buildBackupFileNameInTimeZone(date, fileHashPrefix, backupTimeZone);
|
||||||
await options.progress?.({
|
await options.progress?.({
|
||||||
step: 'archive_ready',
|
step: 'archive_ready',
|
||||||
fileName,
|
fileName,
|
||||||
|
|||||||
@@ -148,32 +148,21 @@ interface BackupExportManifest {
|
|||||||
|
|
||||||
const BACKUP_FILE_HASH_PREFIX_LENGTH = 5;
|
const BACKUP_FILE_HASH_PREFIX_LENGTH = 5;
|
||||||
|
|
||||||
function parseBackupTimestampFromFileName(fileName: string): Date | null {
|
function extractBackupTimestampFromFileName(fileName: string): string | null {
|
||||||
const match = String(fileName || '').match(/nodewarden_backup_(\d{8})_(\d{6})(?:_[0-9a-f]{5})?\.zip$/i);
|
const match = String(fileName || '').match(/nodewarden_backup_(\d{8})_(\d{6})(?:_[0-9a-f]{5})?\.zip$/i);
|
||||||
if (!match) return null;
|
if (!match) return null;
|
||||||
const datePart = match[1];
|
return `${match[1]}_${match[2]}`;
|
||||||
const timePart = match[2];
|
|
||||||
const iso = `${datePart.slice(0, 4)}-${datePart.slice(4, 6)}-${datePart.slice(6, 8)}T${timePart.slice(0, 2)}:${timePart.slice(2, 4)}:${timePart.slice(4, 6)}.000Z`;
|
|
||||||
const parsed = new Date(iso);
|
|
||||||
return Number.isFinite(parsed.getTime()) ? parsed : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildBackupFileName(date: Date, checksumPrefix: string): string {
|
function buildBackupFileName(timestamp: string, checksumPrefix: string): string {
|
||||||
const parts = [
|
return `nodewarden_backup_${timestamp}_${checksumPrefix}.zip`;
|
||||||
date.getUTCFullYear().toString().padStart(4, '0'),
|
|
||||||
(date.getUTCMonth() + 1).toString().padStart(2, '0'),
|
|
||||||
date.getUTCDate().toString().padStart(2, '0'),
|
|
||||||
date.getUTCHours().toString().padStart(2, '0'),
|
|
||||||
date.getUTCMinutes().toString().padStart(2, '0'),
|
|
||||||
date.getUTCSeconds().toString().padStart(2, '0'),
|
|
||||||
];
|
|
||||||
return `nodewarden_backup_${parts[0]}${parts[1]}${parts[2]}_${parts[3]}${parts[4]}${parts[5]}_${checksumPrefix}.zip`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyBackupFileIntegrityName(fileName: string, bytes: Uint8Array): Promise<string> {
|
async function applyBackupFileIntegrityName(fileName: string, bytes: Uint8Array): Promise<string> {
|
||||||
const integrity = await verifyBackupFileIntegrity(bytes, fileName);
|
const integrity = await verifyBackupFileIntegrity(bytes, fileName);
|
||||||
const effectiveDate = parseBackupTimestampFromFileName(fileName) || new Date();
|
const timestamp = extractBackupTimestampFromFileName(fileName);
|
||||||
return buildBackupFileName(effectiveDate, integrity.actualPrefix);
|
if (!timestamp) return fileName;
|
||||||
|
return buildBackupFileName(timestamp, integrity.actualPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function exportAdminBackup(
|
export async function exportAdminBackup(
|
||||||
|
|||||||
Reference in New Issue
Block a user