feat(backups): Add backup import API and improve backup download/import flow in UI
This commit is contained in:
@@ -347,6 +347,8 @@ export class OneboxHttpServer {
|
||||
return await this.handleDeleteBackupRequest(backupId);
|
||||
} else if (path === '/api/backups/restore' && method === 'POST') {
|
||||
return await this.handleRestoreBackupRequest(req);
|
||||
} else if (path === '/api/backups/import' && method === 'POST') {
|
||||
return await this.handleImportBackupRequest(req);
|
||||
} else if (path === '/api/settings/backup-password' && method === 'POST') {
|
||||
return await this.handleSetBackupPasswordRequest(req);
|
||||
} else if (path === '/api/settings/backup-password' && method === 'GET') {
|
||||
@@ -2278,6 +2280,118 @@ export class OneboxHttpServer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a backup from file upload or URL
|
||||
*/
|
||||
private async handleImportBackupRequest(req: Request): Promise<Response> {
|
||||
try {
|
||||
const contentType = req.headers.get('content-type') || '';
|
||||
let filePath: string | null = null;
|
||||
let newServiceName: string | undefined;
|
||||
let tempFile = false;
|
||||
|
||||
if (contentType.includes('multipart/form-data')) {
|
||||
// Handle file upload
|
||||
const formData = await req.formData();
|
||||
const file = formData.get('file');
|
||||
newServiceName = formData.get('newServiceName')?.toString() || undefined;
|
||||
|
||||
if (!file || !(file instanceof File)) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: 'No file provided',
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Validate file extension
|
||||
if (!file.name.endsWith('.tar.enc')) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: 'Invalid file format. Expected .tar.enc file',
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Save to temp location
|
||||
const tempDir = './.nogit/temp-imports';
|
||||
await Deno.mkdir(tempDir, { recursive: true });
|
||||
filePath = `${tempDir}/${Date.now()}-${file.name}`;
|
||||
tempFile = true;
|
||||
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
await Deno.writeFile(filePath, new Uint8Array(arrayBuffer));
|
||||
|
||||
logger.info(`Saved uploaded backup to ${filePath}`);
|
||||
} else {
|
||||
// Handle JSON body with URL
|
||||
const body = await req.json();
|
||||
const { url, newServiceName: serviceName } = body;
|
||||
newServiceName = serviceName;
|
||||
|
||||
if (!url) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: 'URL is required when not uploading a file',
|
||||
}, 400);
|
||||
}
|
||||
|
||||
// Download from URL
|
||||
const tempDir = './.nogit/temp-imports';
|
||||
await Deno.mkdir(tempDir, { recursive: true });
|
||||
|
||||
const urlFilename = url.split('/').pop() || 'backup.tar.enc';
|
||||
filePath = `${tempDir}/${Date.now()}-${urlFilename}`;
|
||||
tempFile = true;
|
||||
|
||||
logger.info(`Downloading backup from ${url}...`);
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) {
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: `Failed to download from URL: ${response.statusText}`,
|
||||
}, 400);
|
||||
}
|
||||
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
await Deno.writeFile(filePath, new Uint8Array(arrayBuffer));
|
||||
|
||||
logger.info(`Downloaded backup to ${filePath}`);
|
||||
}
|
||||
|
||||
// Import using restoreBackup with mode='import'
|
||||
const result = await this.oneboxRef.backupManager.restoreBackup(filePath, {
|
||||
mode: 'import',
|
||||
newServiceName,
|
||||
overwriteExisting: false,
|
||||
skipPlatformData: false,
|
||||
});
|
||||
|
||||
// Clean up temp file
|
||||
if (tempFile && filePath) {
|
||||
try {
|
||||
await Deno.remove(filePath);
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
return this.jsonResponse({
|
||||
success: true,
|
||||
message: `Backup imported successfully as service '${result.service.name}'`,
|
||||
data: {
|
||||
service: result.service,
|
||||
platformResourcesRestored: result.platformResourcesRestored,
|
||||
warnings: result.warnings,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(`Failed to import backup: ${getErrorMessage(error)}`);
|
||||
return this.jsonResponse({
|
||||
success: false,
|
||||
error: getErrorMessage(error) || 'Failed to import backup',
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set backup encryption password
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user