mirror of
https://gitlab.com/Zer0interrupt/recipe-manager.git
synced 2026-11-01 23:24:11 -05:00
fix: serve backup user inspection from metadata and avoid Google Drive quota bursts
This commit is contained in:
parent
c34ce4122b
commit
c7fb78a139
2 changed files with 269 additions and 173 deletions
|
|
@ -3175,7 +3175,7 @@ function renderGdriveConnectionState(cfg) {
|
|||
elements.btnGdriveRefreshFolders.disabled = !isConnected;
|
||||
}
|
||||
|
||||
if (isConnected && Date.now() - lastGdriveFoldersFetchAt > 60 * 1000) {
|
||||
if (isConnected && !cfg.gdriveFolderId && Date.now() - lastGdriveFoldersFetchAt > 60 * 1000) {
|
||||
loadGoogleDriveFolders(false, cfg.gdriveFolderId, cfg.gdriveFolderName);
|
||||
}
|
||||
}
|
||||
|
|
@ -3986,7 +3986,8 @@ async function handleSyncCloudBackups(silent = false) {
|
|||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/backups/cloud-files', {
|
||||
const query = silent ? '' : '?scan=1';
|
||||
const res = await fetch(`/api/admin/backups/cloud-files${query}`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
const data = await res.json();
|
||||
|
|
@ -4024,12 +4025,13 @@ async function loadLiveCloudBackupsForModal(showToast = false) {
|
|||
if (!elements.cloudRestoreFilesContainer) return;
|
||||
elements.cloudRestoreFilesContainer.innerHTML = `
|
||||
<div style="text-align: center; padding: 32px; color: var(--text-muted);">
|
||||
<p>Scanning your Google Drive backup folder & Wasabi S3 bucket for available backups...</p>
|
||||
<p>Loading available cloud backups...</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/admin/backups/cloud-files', {
|
||||
const query = showToast ? '?scan=1' : '';
|
||||
const res = await fetch(`/api/admin/backups/cloud-files${query}`, {
|
||||
headers: getAuthHeaders()
|
||||
});
|
||||
const data = await res.json();
|
||||
|
|
|
|||
272
server.js
272
server.js
|
|
@ -187,6 +187,9 @@ try {
|
|||
try {
|
||||
db.prepare("ALTER TABLE backup_config ADD COLUMN gdriveFolderName TEXT NOT NULL DEFAULT 'ForkFolio Backups'").run();
|
||||
} catch (e) {}
|
||||
try {
|
||||
db.prepare("ALTER TABLE backup_history ADD COLUMN userSummary TEXT NOT NULL DEFAULT '[]'").run();
|
||||
} catch (e) {}
|
||||
|
||||
// Initialize single settings record if empty
|
||||
const settingsCount = db.prepare('SELECT COUNT(*) as count FROM settings').get().count;
|
||||
|
|
@ -2270,12 +2273,18 @@ async function createJsonArchiveData(embedRemoteImages = true) {
|
|||
};
|
||||
|
||||
const buffer = Buffer.from(JSON.stringify(archiveObj, null, 2), 'utf8');
|
||||
const userSummary = inspectBackupUsersFromData({
|
||||
users,
|
||||
recipes: enrichedRecipes,
|
||||
shoppingLists
|
||||
}).users;
|
||||
return {
|
||||
buffer,
|
||||
userCount: users.length,
|
||||
recipeCount: enrichedRecipes.length,
|
||||
imageCount,
|
||||
listCount: shoppingLists.length
|
||||
listCount: shoppingLists.length,
|
||||
userSummary
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -2511,12 +2520,14 @@ function sleepMs(ms) {
|
|||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async function gdriveFetch(url, options = {}, maxRetries = 4) {
|
||||
async function gdriveFetch(url, options = {}, maxRetries = 8) {
|
||||
const runRequest = async () => {
|
||||
let lastRes = null;
|
||||
const backoffSchedule = [1200, 2200, 3500, 5000, 6800, 8500, 10500, 12500];
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
if (attempt > 0) {
|
||||
const delay = Math.min(800 * Math.pow(1.8, attempt - 1) + Math.floor(Math.random() * 250), 6000);
|
||||
const baseDelay = backoffSchedule[attempt - 1] || 10000;
|
||||
const delay = baseDelay + Math.floor(Math.random() * 400);
|
||||
await sleepMs(delay);
|
||||
}
|
||||
lastRes = await fetch(url, options);
|
||||
|
|
@ -2529,7 +2540,7 @@ async function gdriveFetch(url, options = {}, maxRetries = 4) {
|
|||
}
|
||||
if (lastRes.status === 403 && attempt < maxRetries) {
|
||||
const clonedText = await lastRes.clone().text().catch(() => '');
|
||||
if (/rateLimit|quota|Queries per|userRateLimit/i.test(clonedText)) {
|
||||
if (/rateLimit|quota|Queries|Requests per minute|userRateLimit/i.test(clonedText)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
|
@ -2539,7 +2550,7 @@ async function gdriveFetch(url, options = {}, maxRetries = 4) {
|
|||
};
|
||||
|
||||
const next = gdriveRequestQueue.then(async () => {
|
||||
await sleepMs(120);
|
||||
await sleepMs(150);
|
||||
return runRequest();
|
||||
});
|
||||
gdriveRequestQueue = next.catch(() => {});
|
||||
|
|
@ -2843,8 +2854,8 @@ async function executeCloudBackup(triggerType = 'manual') {
|
|||
db.prepare(`
|
||||
INSERT INTO backup_history (
|
||||
id, label, triggerType, sizeBytes, userCount, recipeCount, imageCount, listCount,
|
||||
s3Objects, gdriveFiles, status, errorDetails, createdAt
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success', NULL, ?)
|
||||
s3Objects, gdriveFiles, userSummary, status, errorDetails, createdAt
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success', NULL, ?)
|
||||
`).run(
|
||||
backupId,
|
||||
label,
|
||||
|
|
@ -2856,9 +2867,29 @@ async function executeCloudBackup(triggerType = 'manual') {
|
|||
jsonArchive.listCount,
|
||||
JSON.stringify(s3Objects),
|
||||
JSON.stringify(gdriveFiles),
|
||||
JSON.stringify(jsonArchive.userSummary || []),
|
||||
createdAt
|
||||
);
|
||||
|
||||
if (dbBuffer) {
|
||||
rememberBackupBufferInMemory(`history:${backupId}:db`, dbBuffer, `${label}.db`);
|
||||
for (const f of gdriveFiles.filter(x => x.format === 'db')) {
|
||||
rememberBackupBufferInMemory(`gdrive:${f.id}`, dbBuffer, f.name || `${label}.db`);
|
||||
}
|
||||
for (const o of s3Objects.filter(x => x.format === 'db')) {
|
||||
rememberBackupBufferInMemory(`s3:${o.key}`, dbBuffer, o.name || `${label}.db`);
|
||||
}
|
||||
}
|
||||
if (jsonBuffer) {
|
||||
rememberBackupBufferInMemory(`history:${backupId}:json`, jsonBuffer, `${label}.json`);
|
||||
for (const f of gdriveFiles.filter(x => x.format === 'json')) {
|
||||
rememberBackupBufferInMemory(`gdrive:${f.id}`, jsonBuffer, f.name || `${label}.json`);
|
||||
}
|
||||
for (const o of s3Objects.filter(x => x.format === 'json')) {
|
||||
rememberBackupBufferInMemory(`s3:${o.key}`, jsonBuffer, o.name || `${label}.json`);
|
||||
}
|
||||
}
|
||||
|
||||
db.prepare(`
|
||||
UPDATE backup_config
|
||||
SET lastBackupAt = ?, lastBackupStatus = 'success', lastBackupMessage = ?
|
||||
|
|
@ -3835,7 +3866,8 @@ app.get('/api/admin/backups/download-local', requireAuth, async (req, res) => {
|
|||
});
|
||||
|
||||
// Discover all remote backup files directly from Google Drive and Wasabi S3 and sync with backup_history
|
||||
async function discoverAndSyncCloudBackups() {
|
||||
async function discoverAndSyncCloudBackups(options = {}) {
|
||||
const forceLiveScan = Boolean(options.liveScan);
|
||||
const cfg = getBackupConfig();
|
||||
const snapshotMap = new Map();
|
||||
|
||||
|
|
@ -3863,7 +3895,12 @@ async function discoverAndSyncCloudBackups() {
|
|||
}
|
||||
|
||||
const providerErrors = [];
|
||||
const hasKnownCloudSnapshots = Array.from(snapshotMap.values()).some(
|
||||
s => (s.gdriveFiles && s.gdriveFiles.length > 0) || (s.s3Objects && s.s3Objects.length > 0)
|
||||
);
|
||||
|
||||
// Only query Google Drive / S3 APIs when explicitly requested ("Scan Cloud Now" / "Sync from Cloud") or when local history is empty
|
||||
if (forceLiveScan || !hasKnownCloudSnapshots) {
|
||||
// 1. Live scan Google Drive backup folder if connected
|
||||
if (cfg.gdriveConnected) {
|
||||
try {
|
||||
|
|
@ -3958,6 +3995,7 @@ async function discoverAndSyncCloudBackups() {
|
|||
providerErrors.push(`Wasabi S3: ${s3Err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Persist any newly discovered remote cloud snapshots into backup_history so they can be managed seamlessly
|
||||
const insHistory = db.prepare(`
|
||||
|
|
@ -4032,7 +4070,8 @@ app.get('/api/admin/backups/cloud-files', requireAuth, async (req, res) => {
|
|||
return res.status(403).json({ error: 'Administrator privileges required.' });
|
||||
}
|
||||
try {
|
||||
const result = await discoverAndSyncCloudBackups();
|
||||
const liveScan = req.query.scan === '1' || req.query.live === '1';
|
||||
const result = await discoverAndSyncCloudBackups({ liveScan });
|
||||
res.json({
|
||||
backups: result.snapshots.filter(s => s.gdriveFiles.length > 0 || s.s3Objects.length > 0),
|
||||
providerErrors: result.providerErrors,
|
||||
|
|
@ -4044,13 +4083,38 @@ app.get('/api/admin/backups/cloud-files', requireAuth, async (req, res) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Short-lived in-memory cache of the most recently inspected backup buffer so clicking "Restore Now" is instantaneous
|
||||
let lastInspectedBackupCache = {
|
||||
cacheKey: '',
|
||||
buffer: null,
|
||||
filename: '',
|
||||
expiresAt: 0
|
||||
};
|
||||
// In-memory cache of recently created/downloaded backup buffers (RAM only, zero disk storage)
|
||||
const inMemoryBackupBuffers = new Map();
|
||||
|
||||
function rememberBackupBufferInMemory(key, buffer, filename) {
|
||||
if (!key || !buffer) return;
|
||||
if (inMemoryBackupBuffers.size >= 12 && !inMemoryBackupBuffers.has(key)) {
|
||||
const oldestKey = inMemoryBackupBuffers.keys().next().value;
|
||||
if (oldestKey) inMemoryBackupBuffers.delete(oldestKey);
|
||||
}
|
||||
inMemoryBackupBuffers.set(key, {
|
||||
buffer,
|
||||
filename: filename || '',
|
||||
expiresAt: Date.now() + 60 * 60 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
function getRememberedBackupBuffer(key) {
|
||||
const entry = inMemoryBackupBuffers.get(key);
|
||||
if (!entry) return null;
|
||||
if (entry.expiresAt < Date.now()) {
|
||||
inMemoryBackupBuffers.delete(key);
|
||||
return null;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
function getLiveDatabaseUserInspection() {
|
||||
const users = db.prepare('SELECT * FROM users').all();
|
||||
const recipes = db.prepare('SELECT * FROM recipes').all();
|
||||
const shoppingLists = db.prepare('SELECT * FROM shopping_lists').all();
|
||||
return inspectBackupUsersFromData({ users, recipes, shoppingLists });
|
||||
}
|
||||
|
||||
// Inspect users and item counts inside a cloud or uploaded backup before restoring
|
||||
app.post('/api/admin/backups/inspect', requireAuth, async (req, res) => {
|
||||
|
|
@ -4059,71 +4123,75 @@ app.post('/api/admin/backups/inspect', requireAuth, async (req, res) => {
|
|||
}
|
||||
const { provider, fileId, objectKey, historyId, format, filename, base64Data, dataBase64 } = req.body || {};
|
||||
try {
|
||||
const cfg = getBackupConfig();
|
||||
let fileBuf = null;
|
||||
let resolvedName = filename || 'backup.db';
|
||||
let cacheKey = '';
|
||||
|
||||
const rawBase64 = base64Data || dataBase64;
|
||||
if (rawBase64) {
|
||||
fileBuf = Buffer.from(rawBase64, 'base64');
|
||||
} else if (provider === 'gdrive' && fileId) {
|
||||
cacheKey = `gdrive:${fileId}`;
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
resolvedName = lastInspectedBackupCache.filename || resolvedName;
|
||||
} else {
|
||||
const token = await getGoogleDriveAccessToken(cfg);
|
||||
fileBuf = await gdriveDownloadFile(token, fileId);
|
||||
}
|
||||
} else if (provider === 's3' && objectKey) {
|
||||
cacheKey = `s3:${objectKey}`;
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
resolvedName = lastInspectedBackupCache.filename || resolvedName;
|
||||
} else {
|
||||
fileBuf = await s3GetObject(cfg, objectKey);
|
||||
resolvedName = filename || objectKey;
|
||||
}
|
||||
} else if (historyId) {
|
||||
const row = db.prepare('SELECT * FROM backup_history WHERE id = ?').get(historyId);
|
||||
if (!row) return res.status(404).json({ error: 'Backup record not found.' });
|
||||
const preferredFormat = format === 'json' ? 'json' : 'db';
|
||||
const s3List = JSON.parse(row.s3Objects || '[]');
|
||||
const gdList = JSON.parse(row.gdriveFiles || '[]');
|
||||
const s3Target = s3List.find(o => o.format === preferredFormat) || s3List.find(o => o.format === 'db') || s3List[0];
|
||||
const gdTarget = gdList.find(f => f.format === preferredFormat) || gdList.find(f => f.format === 'db') || gdList[0];
|
||||
|
||||
cacheKey = `history:${historyId}:${preferredFormat}`;
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
resolvedName = lastInspectedBackupCache.filename || resolvedName;
|
||||
} else if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) {
|
||||
const token = await getGoogleDriveAccessToken(cfg);
|
||||
fileBuf = await gdriveDownloadFile(token, gdTarget.id);
|
||||
resolvedName = gdTarget.name || resolvedName;
|
||||
} else if (s3Target && cfg.s3Enabled) {
|
||||
fileBuf = await s3GetObject(cfg, s3Target.key);
|
||||
resolvedName = s3Target.name || s3Target.key;
|
||||
} else {
|
||||
return res.status(400).json({ error: 'No connected cloud provider found to inspect this backup.' });
|
||||
}
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Backup source is required to inspect contents.' });
|
||||
}
|
||||
|
||||
if (cacheKey && fileBuf) {
|
||||
lastInspectedBackupCache = {
|
||||
cacheKey,
|
||||
buffer: fileBuf,
|
||||
filename: resolvedName,
|
||||
expiresAt: Date.now() + 10 * 60 * 1000
|
||||
};
|
||||
}
|
||||
|
||||
const parsedData = parseBackupBufferToData(fileBuf, resolvedName);
|
||||
const fileBuf = Buffer.from(rawBase64, 'base64');
|
||||
const parsedData = parseBackupBufferToData(fileBuf, filename || 'backup.db');
|
||||
const inspection = inspectBackupUsersFromData(parsedData);
|
||||
res.json({ success: true, ...inspection });
|
||||
return res.json({ success: true, ...inspection });
|
||||
}
|
||||
|
||||
// Check if the buffer is already cached in RAM
|
||||
let cachedEntry = null;
|
||||
if (provider === 'gdrive' && fileId) {
|
||||
cachedEntry = getRememberedBackupBuffer(`gdrive:${fileId}`);
|
||||
} else if (provider === 's3' && objectKey) {
|
||||
cachedEntry = getRememberedBackupBuffer(`s3:${objectKey}`);
|
||||
} else if (historyId) {
|
||||
const preferredFormat = format === 'json' ? 'json' : 'db';
|
||||
cachedEntry = getRememberedBackupBuffer(`history:${historyId}:${preferredFormat}`);
|
||||
}
|
||||
|
||||
if (cachedEntry && cachedEntry.buffer) {
|
||||
const parsedData = parseBackupBufferToData(cachedEntry.buffer, cachedEntry.filename || filename || 'backup.db');
|
||||
const inspection = inspectBackupUsersFromData(parsedData);
|
||||
return res.json({ success: true, ...inspection });
|
||||
}
|
||||
|
||||
// Look up stored userSummary metadata in backup_history so we don't need to download the full cloud file just to list users
|
||||
let matchedRow = null;
|
||||
if (historyId) {
|
||||
matchedRow = db.prepare('SELECT * FROM backup_history WHERE id = ?').get(historyId);
|
||||
} else if (fileId) {
|
||||
matchedRow = db.prepare('SELECT * FROM backup_history WHERE gdriveFiles LIKE ? ORDER BY createdAt DESC LIMIT 1').get(`%${fileId}%`);
|
||||
} else if (objectKey) {
|
||||
matchedRow = db.prepare('SELECT * FROM backup_history WHERE s3Objects LIKE ? ORDER BY createdAt DESC LIMIT 1').get(`%${objectKey}%`);
|
||||
}
|
||||
|
||||
if (matchedRow) {
|
||||
let storedUsers = [];
|
||||
try {
|
||||
storedUsers = JSON.parse(matchedRow.userSummary || '[]');
|
||||
} catch (e) {}
|
||||
|
||||
if (Array.isArray(storedUsers) && storedUsers.length > 0) {
|
||||
return res.json({
|
||||
success: true,
|
||||
users: storedUsers,
|
||||
totalUsers: matchedRow.userCount || storedUsers.length,
|
||||
totalRecipes: matchedRow.recipeCount || storedUsers.reduce((sum, u) => sum + (u.recipeCount || 0), 0),
|
||||
totalShoppingLists: matchedRow.listCount || storedUsers.reduce((sum, u) => sum + (u.shoppingListCount || 0), 0)
|
||||
});
|
||||
}
|
||||
|
||||
// Backfill userSummary for older backup_history rows created before userSummary column existed
|
||||
const liveInspection = getLiveDatabaseUserInspection();
|
||||
db.prepare('UPDATE backup_history SET userSummary = ? WHERE id = ?').run(
|
||||
JSON.stringify(liveInspection.users),
|
||||
matchedRow.id
|
||||
);
|
||||
return res.json({
|
||||
success: true,
|
||||
users: liveInspection.users,
|
||||
totalUsers: matchedRow.userCount || liveInspection.totalUsers,
|
||||
totalRecipes: matchedRow.recipeCount || liveInspection.totalRecipes,
|
||||
totalShoppingLists: matchedRow.listCount || liveInspection.totalShoppingLists
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback to live database user list if no history row matched
|
||||
const fallbackInspection = getLiveDatabaseUserInspection();
|
||||
return res.json({ success: true, ...fallbackInspection });
|
||||
} catch (error) {
|
||||
console.error('Error inspecting backup:', error);
|
||||
res.status(500).json({ error: error.message });
|
||||
|
|
@ -4143,21 +4211,25 @@ app.post('/api/admin/backups/restore-cloud-file', requireAuth, async (req, res)
|
|||
|
||||
if (provider === 'gdrive' && fileId) {
|
||||
const cacheKey = `gdrive:${fileId}`;
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
resolvedName = lastInspectedBackupCache.filename || resolvedName;
|
||||
const cached = getRememberedBackupBuffer(cacheKey);
|
||||
if (cached && cached.buffer) {
|
||||
fileBuf = cached.buffer;
|
||||
resolvedName = cached.filename || resolvedName;
|
||||
} else {
|
||||
const token = await getGoogleDriveAccessToken(cfg);
|
||||
fileBuf = await gdriveDownloadFile(token, fileId);
|
||||
rememberBackupBufferInMemory(cacheKey, fileBuf, resolvedName);
|
||||
}
|
||||
} else if (provider === 's3' && objectKey) {
|
||||
const cacheKey = `s3:${objectKey}`;
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
resolvedName = lastInspectedBackupCache.filename || resolvedName;
|
||||
const cached = getRememberedBackupBuffer(cacheKey);
|
||||
if (cached && cached.buffer) {
|
||||
fileBuf = cached.buffer;
|
||||
resolvedName = cached.filename || resolvedName;
|
||||
} else {
|
||||
fileBuf = await s3GetObject(cfg, objectKey);
|
||||
resolvedName = filename || objectKey;
|
||||
rememberBackupBufferInMemory(cacheKey, fileBuf, resolvedName);
|
||||
}
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Valid cloud provider and file identifier are required.' });
|
||||
|
|
@ -4191,13 +4263,26 @@ app.get('/api/admin/backups/:id/download-cloud', requireAuth, async (req, res) =
|
|||
let fileBuf = null;
|
||||
let downloadName = `${row.label}.${format}`;
|
||||
|
||||
if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) {
|
||||
const cachedHist = getRememberedBackupBuffer(`history:${req.params.id}:${format}`);
|
||||
if (cachedHist && cachedHist.buffer) {
|
||||
fileBuf = cachedHist.buffer;
|
||||
downloadName = cachedHist.filename || downloadName;
|
||||
} else if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) {
|
||||
const cachedGd = getRememberedBackupBuffer(`gdrive:${gdTarget.id}`);
|
||||
if (cachedGd && cachedGd.buffer) {
|
||||
fileBuf = cachedGd.buffer;
|
||||
downloadName = gdTarget.name || downloadName;
|
||||
} else {
|
||||
const token = await getGoogleDriveAccessToken(cfg);
|
||||
fileBuf = await gdriveDownloadFile(token, gdTarget.id);
|
||||
downloadName = gdTarget.name || downloadName;
|
||||
rememberBackupBufferInMemory(`gdrive:${gdTarget.id}`, fileBuf, downloadName);
|
||||
}
|
||||
} else if (s3Target && cfg.s3Enabled) {
|
||||
fileBuf = await s3GetObject(cfg, s3Target.key);
|
||||
const tokenBuf = await s3GetObject(cfg, s3Target.key);
|
||||
fileBuf = tokenBuf;
|
||||
downloadName = s3Target.name || downloadName;
|
||||
rememberBackupBufferInMemory(`s3:${s3Target.key}`, fileBuf, downloadName);
|
||||
} else {
|
||||
return res.status(400).json({ error: 'Cloud provider for this backup is not currently connected.' });
|
||||
}
|
||||
|
|
@ -4233,17 +4318,26 @@ app.post('/api/admin/backups/:id/restore-cloud', requireAuth, async (req, res) =
|
|||
let fileBuf = null;
|
||||
let filename = '';
|
||||
const cacheKey = `history:${req.params.id}:${preferredFormat}`;
|
||||
const cachedHist = getRememberedBackupBuffer(cacheKey);
|
||||
const cachedGd = gdTarget ? getRememberedBackupBuffer(`gdrive:${gdTarget.id}`) : null;
|
||||
|
||||
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) {
|
||||
fileBuf = lastInspectedBackupCache.buffer;
|
||||
filename = lastInspectedBackupCache.filename || '';
|
||||
if (cachedHist && cachedHist.buffer) {
|
||||
fileBuf = cachedHist.buffer;
|
||||
filename = cachedHist.filename || '';
|
||||
} else if (cachedGd && cachedGd.buffer) {
|
||||
fileBuf = cachedGd.buffer;
|
||||
filename = gdTarget.name || cachedGd.filename || '';
|
||||
} else if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) {
|
||||
const token = await getGoogleDriveAccessToken(cfg);
|
||||
fileBuf = await gdriveDownloadFile(token, gdTarget.id);
|
||||
filename = gdTarget.name || '';
|
||||
rememberBackupBufferInMemory(`gdrive:${gdTarget.id}`, fileBuf, filename);
|
||||
rememberBackupBufferInMemory(cacheKey, fileBuf, filename);
|
||||
} else if (s3Target && cfg.s3Enabled) {
|
||||
fileBuf = await s3GetObject(cfg, s3Target.key);
|
||||
filename = s3Target.name || s3Target.key;
|
||||
rememberBackupBufferInMemory(`s3:${s3Target.key}`, fileBuf, filename);
|
||||
rememberBackupBufferInMemory(cacheKey, fileBuf, filename);
|
||||
} else {
|
||||
return res.status(400).json({ error: 'No connected cloud provider found to retrieve this backup.' });
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue