fix: serve backup user inspection from metadata and avoid Google Drive quota bursts

This commit is contained in:
Chris 2026-10-31 01:36:47 -04:00
parent c34ce4122b
commit c7fb78a139
2 changed files with 269 additions and 173 deletions

View file

@ -3175,7 +3175,7 @@ function renderGdriveConnectionState(cfg) {
elements.btnGdriveRefreshFolders.disabled = !isConnected; 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); loadGoogleDriveFolders(false, cfg.gdriveFolderId, cfg.gdriveFolderName);
} }
} }
@ -3986,7 +3986,8 @@ async function handleSyncCloudBackups(silent = false) {
} }
try { 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() headers: getAuthHeaders()
}); });
const data = await res.json(); const data = await res.json();
@ -4024,12 +4025,13 @@ async function loadLiveCloudBackupsForModal(showToast = false) {
if (!elements.cloudRestoreFilesContainer) return; if (!elements.cloudRestoreFilesContainer) return;
elements.cloudRestoreFilesContainer.innerHTML = ` elements.cloudRestoreFilesContainer.innerHTML = `
<div style="text-align: center; padding: 32px; color: var(--text-muted);"> <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> </div>
`; `;
try { 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() headers: getAuthHeaders()
}); });
const data = await res.json(); const data = await res.json();

272
server.js
View file

@ -187,6 +187,9 @@ try {
try { try {
db.prepare("ALTER TABLE backup_config ADD COLUMN gdriveFolderName TEXT NOT NULL DEFAULT 'ForkFolio Backups'").run(); db.prepare("ALTER TABLE backup_config ADD COLUMN gdriveFolderName TEXT NOT NULL DEFAULT 'ForkFolio Backups'").run();
} catch (e) {} } 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 // Initialize single settings record if empty
const settingsCount = db.prepare('SELECT COUNT(*) as count FROM settings').get().count; 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 buffer = Buffer.from(JSON.stringify(archiveObj, null, 2), 'utf8');
const userSummary = inspectBackupUsersFromData({
users,
recipes: enrichedRecipes,
shoppingLists
}).users;
return { return {
buffer, buffer,
userCount: users.length, userCount: users.length,
recipeCount: enrichedRecipes.length, recipeCount: enrichedRecipes.length,
imageCount, imageCount,
listCount: shoppingLists.length listCount: shoppingLists.length,
userSummary
}; };
} }
@ -2511,12 +2520,14 @@ function sleepMs(ms) {
return new Promise(resolve => setTimeout(resolve, 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 () => { const runRequest = async () => {
let lastRes = null; let lastRes = null;
const backoffSchedule = [1200, 2200, 3500, 5000, 6800, 8500, 10500, 12500];
for (let attempt = 0; attempt <= maxRetries; attempt++) { for (let attempt = 0; attempt <= maxRetries; attempt++) {
if (attempt > 0) { 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); await sleepMs(delay);
} }
lastRes = await fetch(url, options); lastRes = await fetch(url, options);
@ -2529,7 +2540,7 @@ async function gdriveFetch(url, options = {}, maxRetries = 4) {
} }
if (lastRes.status === 403 && attempt < maxRetries) { if (lastRes.status === 403 && attempt < maxRetries) {
const clonedText = await lastRes.clone().text().catch(() => ''); 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; continue;
} }
} }
@ -2539,7 +2550,7 @@ async function gdriveFetch(url, options = {}, maxRetries = 4) {
}; };
const next = gdriveRequestQueue.then(async () => { const next = gdriveRequestQueue.then(async () => {
await sleepMs(120); await sleepMs(150);
return runRequest(); return runRequest();
}); });
gdriveRequestQueue = next.catch(() => {}); gdriveRequestQueue = next.catch(() => {});
@ -2843,8 +2854,8 @@ async function executeCloudBackup(triggerType = 'manual') {
db.prepare(` db.prepare(`
INSERT INTO backup_history ( INSERT INTO backup_history (
id, label, triggerType, sizeBytes, userCount, recipeCount, imageCount, listCount, id, label, triggerType, sizeBytes, userCount, recipeCount, imageCount, listCount,
s3Objects, gdriveFiles, status, errorDetails, createdAt s3Objects, gdriveFiles, userSummary, status, errorDetails, createdAt
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success', NULL, ?) ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'success', NULL, ?)
`).run( `).run(
backupId, backupId,
label, label,
@ -2856,9 +2867,29 @@ async function executeCloudBackup(triggerType = 'manual') {
jsonArchive.listCount, jsonArchive.listCount,
JSON.stringify(s3Objects), JSON.stringify(s3Objects),
JSON.stringify(gdriveFiles), JSON.stringify(gdriveFiles),
JSON.stringify(jsonArchive.userSummary || []),
createdAt 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(` db.prepare(`
UPDATE backup_config UPDATE backup_config
SET lastBackupAt = ?, lastBackupStatus = 'success', lastBackupMessage = ? 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 // 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 cfg = getBackupConfig();
const snapshotMap = new Map(); const snapshotMap = new Map();
@ -3863,7 +3895,12 @@ async function discoverAndSyncCloudBackups() {
} }
const providerErrors = []; 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 // 1. Live scan Google Drive backup folder if connected
if (cfg.gdriveConnected) { if (cfg.gdriveConnected) {
try { try {
@ -3958,6 +3995,7 @@ async function discoverAndSyncCloudBackups() {
providerErrors.push(`Wasabi S3: ${s3Err.message}`); providerErrors.push(`Wasabi S3: ${s3Err.message}`);
} }
} }
}
// Persist any newly discovered remote cloud snapshots into backup_history so they can be managed seamlessly // Persist any newly discovered remote cloud snapshots into backup_history so they can be managed seamlessly
const insHistory = db.prepare(` 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.' }); return res.status(403).json({ error: 'Administrator privileges required.' });
} }
try { try {
const result = await discoverAndSyncCloudBackups(); const liveScan = req.query.scan === '1' || req.query.live === '1';
const result = await discoverAndSyncCloudBackups({ liveScan });
res.json({ res.json({
backups: result.snapshots.filter(s => s.gdriveFiles.length > 0 || s.s3Objects.length > 0), backups: result.snapshots.filter(s => s.gdriveFiles.length > 0 || s.s3Objects.length > 0),
providerErrors: result.providerErrors, 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 // In-memory cache of recently created/downloaded backup buffers (RAM only, zero disk storage)
let lastInspectedBackupCache = { const inMemoryBackupBuffers = new Map();
cacheKey: '',
buffer: null, function rememberBackupBufferInMemory(key, buffer, filename) {
filename: '', if (!key || !buffer) return;
expiresAt: 0 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 // Inspect users and item counts inside a cloud or uploaded backup before restoring
app.post('/api/admin/backups/inspect', requireAuth, async (req, res) => { 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 || {}; const { provider, fileId, objectKey, historyId, format, filename, base64Data, dataBase64 } = req.body || {};
try { try {
const cfg = getBackupConfig();
let fileBuf = null;
let resolvedName = filename || 'backup.db';
let cacheKey = '';
const rawBase64 = base64Data || dataBase64; const rawBase64 = base64Data || dataBase64;
if (rawBase64) { if (rawBase64) {
fileBuf = Buffer.from(rawBase64, 'base64'); const fileBuf = Buffer.from(rawBase64, 'base64');
} else if (provider === 'gdrive' && fileId) { const parsedData = parseBackupBufferToData(fileBuf, filename || 'backup.db');
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 inspection = inspectBackupUsersFromData(parsedData); 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) { } catch (error) {
console.error('Error inspecting backup:', error); console.error('Error inspecting backup:', error);
res.status(500).json({ error: error.message }); 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) { if (provider === 'gdrive' && fileId) {
const cacheKey = `gdrive:${fileId}`; const cacheKey = `gdrive:${fileId}`;
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) { const cached = getRememberedBackupBuffer(cacheKey);
fileBuf = lastInspectedBackupCache.buffer; if (cached && cached.buffer) {
resolvedName = lastInspectedBackupCache.filename || resolvedName; fileBuf = cached.buffer;
resolvedName = cached.filename || resolvedName;
} else { } else {
const token = await getGoogleDriveAccessToken(cfg); const token = await getGoogleDriveAccessToken(cfg);
fileBuf = await gdriveDownloadFile(token, fileId); fileBuf = await gdriveDownloadFile(token, fileId);
rememberBackupBufferInMemory(cacheKey, fileBuf, resolvedName);
} }
} else if (provider === 's3' && objectKey) { } else if (provider === 's3' && objectKey) {
const cacheKey = `s3:${objectKey}`; const cacheKey = `s3:${objectKey}`;
if (lastInspectedBackupCache.cacheKey === cacheKey && lastInspectedBackupCache.buffer && lastInspectedBackupCache.expiresAt > Date.now()) { const cached = getRememberedBackupBuffer(cacheKey);
fileBuf = lastInspectedBackupCache.buffer; if (cached && cached.buffer) {
resolvedName = lastInspectedBackupCache.filename || resolvedName; fileBuf = cached.buffer;
resolvedName = cached.filename || resolvedName;
} else { } else {
fileBuf = await s3GetObject(cfg, objectKey); fileBuf = await s3GetObject(cfg, objectKey);
resolvedName = filename || objectKey; resolvedName = filename || objectKey;
rememberBackupBufferInMemory(cacheKey, fileBuf, resolvedName);
} }
} else { } else {
return res.status(400).json({ error: 'Valid cloud provider and file identifier are required.' }); 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 fileBuf = null;
let downloadName = `${row.label}.${format}`; 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); const token = await getGoogleDriveAccessToken(cfg);
fileBuf = await gdriveDownloadFile(token, gdTarget.id); fileBuf = await gdriveDownloadFile(token, gdTarget.id);
downloadName = gdTarget.name || downloadName; downloadName = gdTarget.name || downloadName;
rememberBackupBufferInMemory(`gdrive:${gdTarget.id}`, fileBuf, downloadName);
}
} else if (s3Target && cfg.s3Enabled) { } else if (s3Target && cfg.s3Enabled) {
fileBuf = await s3GetObject(cfg, s3Target.key); const tokenBuf = await s3GetObject(cfg, s3Target.key);
fileBuf = tokenBuf;
downloadName = s3Target.name || downloadName; downloadName = s3Target.name || downloadName;
rememberBackupBufferInMemory(`s3:${s3Target.key}`, fileBuf, downloadName);
} else { } else {
return res.status(400).json({ error: 'Cloud provider for this backup is not currently connected.' }); 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 fileBuf = null;
let filename = ''; let filename = '';
const cacheKey = `history:${req.params.id}:${preferredFormat}`; 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()) { if (cachedHist && cachedHist.buffer) {
fileBuf = lastInspectedBackupCache.buffer; fileBuf = cachedHist.buffer;
filename = lastInspectedBackupCache.filename || ''; filename = cachedHist.filename || '';
} else if (cachedGd && cachedGd.buffer) {
fileBuf = cachedGd.buffer;
filename = gdTarget.name || cachedGd.filename || '';
} else if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) { } else if (gdTarget && (cfg.gdriveEnabled || cfg.gdriveConnected)) {
const token = await getGoogleDriveAccessToken(cfg); const token = await getGoogleDriveAccessToken(cfg);
fileBuf = await gdriveDownloadFile(token, gdTarget.id); fileBuf = await gdriveDownloadFile(token, gdTarget.id);
filename = gdTarget.name || ''; filename = gdTarget.name || '';
rememberBackupBufferInMemory(`gdrive:${gdTarget.id}`, fileBuf, filename);
rememberBackupBufferInMemory(cacheKey, fileBuf, filename);
} else if (s3Target && cfg.s3Enabled) { } else if (s3Target && cfg.s3Enabled) {
fileBuf = await s3GetObject(cfg, s3Target.key); fileBuf = await s3GetObject(cfg, s3Target.key);
filename = s3Target.name || s3Target.key; filename = s3Target.name || s3Target.key;
rememberBackupBufferInMemory(`s3:${s3Target.key}`, fileBuf, filename);
rememberBackupBufferInMemory(cacheKey, fileBuf, filename);
} else { } else {
return res.status(400).json({ error: 'No connected cloud provider found to retrieve this backup.' }); return res.status(400).json({ error: 'No connected cloud provider found to retrieve this backup.' });
} }