From d7f698ead739096c1515facdc081108b5d8a1438 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Mon, 15 Sep 2025 12:00:26 +0100 Subject: [PATCH] File migration --- frontend/src/services/indexedDBManager.ts | 138 ++++++++++++++++++---- frontend/src/types/file.ts | 4 +- frontend/src/types/fileContext.ts | 1 + 3 files changed, 117 insertions(+), 26 deletions(-) diff --git a/frontend/src/services/indexedDBManager.ts b/frontend/src/services/indexedDBManager.ts index 8b9be040f..0c63aec19 100644 --- a/frontend/src/services/indexedDBManager.ts +++ b/frontend/src/services/indexedDBManager.ts @@ -87,45 +87,135 @@ class IndexedDBManager { request.onupgradeneeded = (event) => { const db = request.result; const oldVersion = event.oldVersion; + const transaction = request.transaction; console.log(`Upgrading ${config.name} from v${oldVersion} to v${config.version}`); // Create or update object stores config.stores.forEach(storeConfig => { + let store: IDBObjectStore | undefined; + if (db.objectStoreNames.contains(storeConfig.name)) { - // Store exists - for now, just continue (could add migration logic here) + // Store exists - get reference for migration console.log(`Object store '${storeConfig.name}' already exists`); - return; + store = transaction?.objectStore(storeConfig.name); + + // Add new indexes if they don't exist + if (storeConfig.indexes && store) { + storeConfig.indexes.forEach(indexConfig => { + if (!store?.indexNames.contains(indexConfig.name)) { + store?.createIndex( + indexConfig.name, + indexConfig.keyPath, + { unique: indexConfig.unique } + ); + console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`); + } + }); + } + } else { + // Create new object store + const options: IDBObjectStoreParameters = {}; + if (storeConfig.keyPath) { + options.keyPath = storeConfig.keyPath; + } + if (storeConfig.autoIncrement) { + options.autoIncrement = storeConfig.autoIncrement; + } + + store = db.createObjectStore(storeConfig.name, options); + console.log(`Created object store '${storeConfig.name}'`); + + // Create indexes + if (storeConfig.indexes) { + storeConfig.indexes.forEach(indexConfig => { + store?.createIndex( + indexConfig.name, + indexConfig.keyPath, + { unique: indexConfig.unique } + ); + console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`); + }); + } } - // Create new object store - const options: IDBObjectStoreParameters = {}; - if (storeConfig.keyPath) { - options.keyPath = storeConfig.keyPath; - } - if (storeConfig.autoIncrement) { - options.autoIncrement = storeConfig.autoIncrement; - } - - const store = db.createObjectStore(storeConfig.name, options); - console.log(`Created object store '${storeConfig.name}'`); - - // Create indexes - if (storeConfig.indexes) { - storeConfig.indexes.forEach(indexConfig => { - store.createIndex( - indexConfig.name, - indexConfig.keyPath, - { unique: indexConfig.unique } - ); - console.log(`Created index '${indexConfig.name}' on '${storeConfig.name}'`); - }); + // Perform data migration for files database + if (config.name === 'stirling-pdf-files' && storeConfig.name === 'files' && store) { + this.migrateFileHistoryFields(store, oldVersion); } }); }; }); } + /** + * Migrate existing file records to include new file history fields + */ + private migrateFileHistoryFields(store: IDBObjectStore, oldVersion: number): void { + // Only migrate if upgrading from a version before file history was added (version < 3) + if (oldVersion >= 3) { + return; + } + + console.log('Starting file history migration for existing records...'); + + const cursor = store.openCursor(); + let migratedCount = 0; + + cursor.onsuccess = (event) => { + const cursor = (event.target as IDBRequest).result; + if (cursor) { + const record = cursor.value; + let needsUpdate = false; + + // Add missing file history fields with sensible defaults + if (record.isLeaf === undefined) { + record.isLeaf = true; // Existing files are unprocessed, should appear in recent files + needsUpdate = true; + } + + if (record.versionNumber === undefined) { + record.versionNumber = 1; // Existing files are first version + needsUpdate = true; + } + + if (record.originalFileId === undefined) { + record.originalFileId = record.id; // Existing files are their own root + needsUpdate = true; + } + + if (record.parentFileId === undefined) { + record.parentFileId = undefined; // No parent for existing files + needsUpdate = true; + } + + if (record.toolHistory === undefined) { + record.toolHistory = []; // No history for existing files + needsUpdate = true; + } + + // Update the record if any fields were missing + if (needsUpdate) { + try { + cursor.update(record); + migratedCount++; + } catch (error) { + console.error('Failed to migrate record:', record.id, error); + } + } + + cursor.continue(); + } else { + // Migration complete + console.log(`File history migration completed. Migrated ${migratedCount} records.`); + } + }; + + cursor.onerror = (event) => { + console.error('File history migration failed:', (event.target as IDBRequest).error); + }; + } + /** * Get database connection (must be already opened) */ diff --git a/frontend/src/types/file.ts b/frontend/src/types/file.ts index 289292433..edbc4b364 100644 --- a/frontend/src/types/file.ts +++ b/frontend/src/types/file.ts @@ -28,8 +28,8 @@ export interface BaseFileMetadata { createdAt?: number; // When file was added to system // File history tracking - isLeaf?: boolean; // True if this file hasn't been processed yet - originalFileId?: string; // Root file ID for grouping versions + isLeaf: boolean; // True if this file hasn't been processed yet + originalFileId: string; // Root file ID for grouping versions versionNumber: number; // Version number in chain parentFileId?: FileId; // Immediate parent file ID toolHistory?: Array<{ diff --git a/frontend/src/types/fileContext.ts b/frontend/src/types/fileContext.ts index cbb59e05c..a7a745c5f 100644 --- a/frontend/src/types/fileContext.ts +++ b/frontend/src/types/fileContext.ts @@ -169,6 +169,7 @@ export function createNewStirlingFileStub( size: file.size, type: file.type, lastModified: file.lastModified, + originalFileId: fileId, quickKey: createQuickKey(file), createdAt: Date.now(), isLeaf: true, // New files are leaf nodes by default