From d07a47010b4234579cc1da4561865b24b2749d8d Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2026 21:55:29 +0000 Subject: [PATCH 1/5] fix: implement actual data collection for backup security manager Co-authored-by: daggerstuff <261005129+daggerstuff@users.noreply.github.com> --- src/lib/security/backup/index.ts | 84 +++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 8 deletions(-) diff --git a/src/lib/security/backup/index.ts b/src/lib/security/backup/index.ts index 8fbee8582..cc762d484 100644 --- a/src/lib/security/backup/index.ts +++ b/src/lib/security/backup/index.ts @@ -168,6 +168,7 @@ const logger = createBuildSafeLogger('backup-security') // Current version of the encryption implementation const ENCRYPTION_VERSION = '1.0' +import mongoose from "mongoose" import { BackupType, BackupStatus, StorageLocation } from './backup-types' export interface BackupRetentionPolicy { @@ -798,7 +799,7 @@ export class BackupSecurityManager { } } catch (error: unknown) { logger.error( - `Error searching for backup metadata in ${location}: ${error instanceof Error ? String(error) : String(error)}`, + `Error searching for backup metadata in ${location}: ${error instanceof Error ? error.message : String(error)}`, ) } } @@ -809,16 +810,83 @@ export class BackupSecurityManager { /** * Get data to backup based on backup type */ + private async getLastBackupTime(requireFull: boolean = false): Promise { + let latestTimestamp = 0; + + const storageEntries = Array.from(this.storageProviders.entries()) + for (let i = 0; i < storageEntries.length; i++) { + const entry = storageEntries[i] + if (!entry || !Array.isArray(entry) || entry.length < 2) continue + + const [location, provider] = entry + if (!provider) continue + + try { + const files = await provider.listFiles("backups/") // Fallback to list all and filter if pattern not supported + const metaFiles = files.filter(f => f.endsWith(".meta.json")) + for (const file of metaFiles) { + try { + const metadataBuffer = await provider.getFile(file) + const metadata = JSON.parse(new TextDecoder().decode(metadataBuffer)) as BackupMetadata + + if (metadata.status === BackupStatus.COMPLETED) { + if (requireFull && metadata.type !== BackupType.FULL) continue + if (!requireFull && metadata.type !== BackupType.FULL && metadata.type !== BackupType.DIFFERENTIAL && metadata.type !== BackupType.INCREMENTAL) continue + + const timestamp = new Date(metadata.timestamp).getTime() + if (timestamp > latestTimestamp) { + latestTimestamp = timestamp + } + } + } catch (e) { + // Ignore individual file parsing errors + } + } + } catch (error: unknown) { + logger.error(`Error searching for latest backup metadata: ${error instanceof Error ? error.message : String(error)}`) + } + } + + return latestTimestamp > 0 ? new Date(latestTimestamp) : null; + } + private async getDataForBackup(type: BackupType): Promise { - // Implementation would collect app data based on backup type - // For now return dummy data for demonstration - // [PIX-44] TODO: No more fucking cop-outs - const dummyData = { - message: `This is a ${type} backup created at ${new Date().toISOString()}`, + const backupData: Record = { + timestamp: new Date().toISOString(), + type, + data: {} + } + + try { + let query = {} + + if (type === BackupType.DIFFERENTIAL || type === BackupType.INCREMENTAL) { + const requireFull = type === BackupType.DIFFERENTIAL + const lastBackupTime = await this.getLastBackupTime(requireFull) + const baselineTime = lastBackupTime || new Date(Date.now() - 24 * 60 * 60 * 1000) + query = { updatedAt: { $gte: baselineTime } } + } + + const models = mongoose.modelNames() + const dataPromises = models.map(async (modelName) => { + const Model = mongoose.model(modelName) + const docs = await Model.find(query).lean().exec() + return { modelName, docs } + }) + + const results = await Promise.all(dataPromises) + + for (const result of results) { + if (result.docs.length > 0) { + backupData.data[result.modelName] = result.docs + } + } + } catch (error: unknown) { + logger.error(`Failed to collect data for backup: ${error instanceof Error ? error.message : String(error)}`) + throw new Error(`Data collection failed: ${error instanceof Error ? error.message : String(error)}`) } - // Use TextEncoder for cross-environment compatibility - return new TextEncoder().encode(JSON.stringify(dummyData)) + return new TextEncoder().encode(JSON.stringify(backupData)) } /** From 41e59c754295462b2f51d8e7f71fe9bd70c3313f Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 08:42:06 +0000 Subject: [PATCH 2/5] fix: implement actual data collection for backup security manager Co-authored-by: daggerstuff <261005129+daggerstuff@users.noreply.github.com> --- src/lib/security/backup/index.ts | 95 ++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/src/lib/security/backup/index.ts b/src/lib/security/backup/index.ts index cc762d484..69009d329 100644 --- a/src/lib/security/backup/index.ts +++ b/src/lib/security/backup/index.ts @@ -168,7 +168,6 @@ const logger = createBuildSafeLogger('backup-security') // Current version of the encryption implementation const ENCRYPTION_VERSION = '1.0' -import mongoose from "mongoose" import { BackupType, BackupStatus, StorageLocation } from './backup-types' export interface BackupRetentionPolicy { @@ -810,40 +809,37 @@ export class BackupSecurityManager { /** * Get data to backup based on backup type */ - private async getLastBackupTime(requireFull: boolean = false): Promise { + private async getLastBackupTime(requireFull = false): Promise { let latestTimestamp = 0; - const storageEntries = Array.from(this.storageProviders.entries()) + const storageEntries = Array.from(this.storageProviders.entries()); for (let i = 0; i < storageEntries.length; i++) { - const entry = storageEntries[i] - if (!entry || !Array.isArray(entry) || entry.length < 2) continue - - const [location, provider] = entry - if (!provider) continue + const [, provider] = storageEntries[i]; + if (!provider) continue; try { - const files = await provider.listFiles("backups/") // Fallback to list all and filter if pattern not supported - const metaFiles = files.filter(f => f.endsWith(".meta.json")) - for (const file of metaFiles) { + const files = await provider.listFiles("backups/"); + const metaFiles = files.filter((f) => f.endsWith(".meta.json")); + for (const metaFile of metaFiles) { try { - const metadataBuffer = await provider.getFile(file) - const metadata = JSON.parse(new TextDecoder().decode(metadataBuffer)) as BackupMetadata + const metadataBuffer = await provider.getFile(metaFile); + const metadata = JSON.parse(new TextDecoder().decode(metadataBuffer)) as BackupMetadata; if (metadata.status === BackupStatus.COMPLETED) { - if (requireFull && metadata.type !== BackupType.FULL) continue - if (!requireFull && metadata.type !== BackupType.FULL && metadata.type !== BackupType.DIFFERENTIAL && metadata.type !== BackupType.INCREMENTAL) continue + if (requireFull && metadata.type !== BackupType.FULL) continue; + if (!requireFull && metadata.type !== BackupType.FULL && metadata.type !== BackupType.DIFFERENTIAL && metadata.type !== BackupType.INCREMENTAL) continue; - const timestamp = new Date(metadata.timestamp).getTime() + const timestamp = new Date(metadata.timestamp).getTime(); if (timestamp > latestTimestamp) { - latestTimestamp = timestamp + latestTimestamp = timestamp; } } - } catch (e) { + } catch { // Ignore individual file parsing errors } } } catch (error: unknown) { - logger.error(`Error searching for latest backup metadata: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Error searching for latest backup metadata: ${error instanceof Error ? error.message : String(error)}`); } } @@ -851,42 +847,57 @@ export class BackupSecurityManager { } private async getDataForBackup(type: BackupType): Promise { - const backupData: Record = { - timestamp: new Date().toISOString(), - type, - data: {} - } + let appDataJson = '{"timestamp":"' + new Date().toISOString() + '","type":"' + type + '","data":{'; try { - let query = {} + const mongooseModule = "mongoose"; + const mongoose = (await import(/* @vite-ignore */ mongooseModule)).default || await import(/* @vite-ignore */ mongooseModule); + const models = mongoose.modelNames(); + + let isFirstModel = true; + let baselineTime: Date | null = null; if (type === BackupType.DIFFERENTIAL || type === BackupType.INCREMENTAL) { - const requireFull = type === BackupType.DIFFERENTIAL - const lastBackupTime = await this.getLastBackupTime(requireFull) - const baselineTime = lastBackupTime || new Date(Date.now() - 24 * 60 * 60 * 1000) - query = { updatedAt: { $gte: baselineTime } } + const requireFull = type === BackupType.DIFFERENTIAL; + const lastBackupTime = await this.getLastBackupTime(requireFull); + baselineTime = lastBackupTime || new Date(Date.now() - 24 * 60 * 60 * 1000); } - const models = mongoose.modelNames() - const dataPromises = models.map(async (modelName) => { - const Model = mongoose.model(modelName) - const docs = await Model.find(query).lean().exec() - return { modelName, docs } - }) + for (const modelName of models) { + const Model = mongoose.model(modelName); + const query: Record = {}; - const results = await Promise.all(dataPromises) + if (baselineTime && Model.schema.paths.updatedAt) { + query.updatedAt = { $gte: baselineTime }; + } - for (const result of results) { - if (result.docs.length > 0) { - backupData.data[result.modelName] = result.docs + if (!isFirstModel) { + appDataJson += ','; } + appDataJson += '"' + modelName + '":['; + isFirstModel = false; + + const cursor = Model.find(query).lean().cursor(); + let isFirstDoc = true; + + for await (const doc of cursor) { + if (!isFirstDoc) { + appDataJson += ','; + } + appDataJson += JSON.stringify(doc); + isFirstDoc = false; + } + + appDataJson += ']'; } + + appDataJson += '}}'; } catch (error: unknown) { - logger.error(`Failed to collect data for backup: ${error instanceof Error ? error.message : String(error)}`) - throw new Error(`Data collection failed: ${error instanceof Error ? error.message : String(error)}`) + logger.error(`Failed to collect data for backup: ${error instanceof Error ? error.message : String(error)}`); + throw error; // Fail loudly to prevent silent data corruption } - return new TextEncoder().encode(JSON.stringify(backupData)) + return new TextEncoder().encode(appDataJson); } /** From 29312af6b05982d16d0d87bcc2d8688e46222f65 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:22:46 +0000 Subject: [PATCH 3/5] fix: implement actual data collection for backup security manager Co-authored-by: daggerstuff <261005129+daggerstuff@users.noreply.github.com> From 42a1b8822f39cfc82985dfb1dfa05547cff8cf21 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 09:42:00 +0000 Subject: [PATCH 4/5] fix: implement actual data collection for backup security manager Co-authored-by: daggerstuff <261005129+daggerstuff@users.noreply.github.com> From cac664097642ea6beb462593aa0d814495599de4 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:34:04 +0000 Subject: [PATCH 5/5] fix: implement actual data collection for backup security manager Co-authored-by: daggerstuff <261005129+daggerstuff@users.noreply.github.com>