mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-23 23:46:35 +00:00
refactor(backup): download through storage sendToResponse
This commit is contained in:
@@ -28,6 +28,7 @@ import { AutoBackupSettingsDto } from './backup.dto';
|
||||
import { getClientIp } from '../audit/client-ip';
|
||||
import { AuditService } from '../audit/audit.service';
|
||||
import { getUploadTmpDir, MAX_BACKUP_UPLOAD_SIZE } from './backup.impl';
|
||||
import { StorageNotFoundError } from '../storage/storage.types';
|
||||
import { ManagedForbidden, isManagedBlocked, MANAGED_FORBIDDEN_ERROR } from '../common/managed';
|
||||
import { RuntimeEnvService } from '../app-config/runtime-env.service';
|
||||
|
||||
@@ -91,7 +92,16 @@ export class BackupController {
|
||||
if (!(await this.backup.backupFileExists(filename))) {
|
||||
throw new HttpException({ error: 'Backup not found' }, 404);
|
||||
}
|
||||
res.download(this.backup.backupFilePath(filename), filename);
|
||||
try {
|
||||
await this.backup.sendBackupToResponse(filename, res);
|
||||
} catch (err) {
|
||||
// The pre-check above owns the normal miss; this only covers a delete
|
||||
// racing between the check and the send.
|
||||
if (err instanceof StorageNotFoundError) {
|
||||
throw new HttpException({ error: 'Backup not found' }, 404);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
@ManagedForbidden('a restore replaces database and uploads, and the operator owns the recovery point')
|
||||
|
||||
@@ -10,6 +10,7 @@ import { invalidatePermissionsCache } from '../permissions/permissions-cache';
|
||||
import { pluginsCodeRoot, pluginsDataRoot } from '../plugins/paths';
|
||||
import { stageExtractedPluginTrees, applyStagedRestoreNow } from '../plugins/plugin-backup';
|
||||
import { snapshotAllPluginDataDbs } from '../plugins/host/plugin-data.service';
|
||||
import type { Response } from 'express';
|
||||
import type { StorageService } from '../storage/storage.service';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -89,6 +90,17 @@ export function backupFileExists(storage: StorageService, filename: string): Pro
|
||||
return storage.exists('backups', filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* The codebase's only res.download becomes the storage equivalent: root-relative
|
||||
* sendFile via sendToResponse, with res.download's attachment header rebuilt by
|
||||
* hand (filenames are regex-gated ASCII — no encoding cases).
|
||||
*/
|
||||
export function sendBackupToResponse(storage: StorageService, filename: string, res: Response): Promise<void> {
|
||||
return storage.sendToResponse('backups', filename, res, {
|
||||
disposition: `attachment; filename="${filename}"`,
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Rate limiter state (shared across requests)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { StorageService } from '../storage/storage.service';
|
||||
import * as svc from './backup.impl';
|
||||
|
||||
@@ -24,6 +25,7 @@ export class BackupService {
|
||||
isValidBackupFilename(filename: string) { return svc.isValidBackupFilename(filename); }
|
||||
backupFilePath(filename: string) { return svc.backupFilePath(filename); }
|
||||
backupFileExists(filename: string) { return svc.backupFileExists(this.storage, filename); }
|
||||
sendBackupToResponse(filename: string, res: Response) { return svc.sendBackupToResponse(this.storage, filename, res); }
|
||||
checkRateLimit(key: string, maxAttempts: number, windowMs: number) { return svc.checkRateLimit(key, maxAttempts, windowMs); }
|
||||
|
||||
get rateWindow() { return svc.BACKUP_RATE_WINDOW; }
|
||||
|
||||
Reference in New Issue
Block a user