diff --git a/package-lock.json b/package-lock.json index 5c96deee..edcd2365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28627,9 +28627,9 @@ } }, "node_modules/terser": { - "version": "5.43.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.0.tgz", - "integrity": "sha512-CqNNxKSGKSZCunSvwKLTs8u8sGGlp27sxNZ4quGh0QeNuyHM0JSEM/clM9Mf4zUp6J+tO2gUXhgXT2YMMkwfKQ==", + "version": "5.43.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz", + "integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==", "devOptional": true, "license": "BSD-2-Clause", "dependencies": { diff --git a/src/services/migrationService.ts b/src/services/migrationService.ts index 3802d166..bf109f30 100644 --- a/src/services/migrationService.ts +++ b/src/services/migrationService.ts @@ -24,7 +24,7 @@ import { PlatformServiceFactory } from "./PlatformServiceFactory"; import { db, accountsDBPromise } from "../db/index"; import { Contact, ContactMethod } from "../db/tables/contacts"; -import { Settings } from "../db/tables/settings"; +import { Settings, MASTER_SETTINGS_KEY } from "../db/tables/settings"; import { Account } from "../db/tables/accounts"; import { logger } from "../utils/logger"; import { parseJsonField } from "../db/databaseUtil"; @@ -285,42 +285,47 @@ export async function getSqliteSettings(): Promise { return []; } - const settings = result.values.map((row) => { - const setting = parseJsonField(row, {}) as Settings; - return { - id: setting.id, - accountDid: setting.accountDid || "", - activeDid: setting.activeDid || "", - apiServer: setting.apiServer || "", - filterFeedByNearby: setting.filterFeedByNearby || false, - filterFeedByVisible: setting.filterFeedByVisible || false, - finishedOnboarding: setting.finishedOnboarding || false, - firstName: setting.firstName || "", - hideRegisterPromptOnNewContact: - setting.hideRegisterPromptOnNewContact || false, - isRegistered: setting.isRegistered || false, - lastName: setting.lastName || "", - lastAckedOfferToUserJwtId: setting.lastAckedOfferToUserJwtId || "", - lastAckedOfferToUserProjectsJwtId: - setting.lastAckedOfferToUserProjectsJwtId || "", - lastNotifiedClaimId: setting.lastNotifiedClaimId || "", - lastViewedClaimId: setting.lastViewedClaimId || "", - notifyingNewActivityTime: setting.notifyingNewActivityTime || "", - notifyingReminderMessage: setting.notifyingReminderMessage || "", - notifyingReminderTime: setting.notifyingReminderTime || "", - partnerApiServer: setting.partnerApiServer || "", - passkeyExpirationMinutes: setting.passkeyExpirationMinutes, - profileImageUrl: setting.profileImageUrl || "", - searchBoxes: parseJsonField(setting.searchBoxes, []), - showContactGivesInline: setting.showContactGivesInline || false, - showGeneralAdvanced: setting.showGeneralAdvanced || false, - showShortcutBvc: setting.showShortcutBvc || false, - vapid: setting.vapid || "", - warnIfProdServer: setting.warnIfProdServer || false, - warnIfTestServer: setting.warnIfTestServer || false, - webPushServer: setting.webPushServer || "", - } as Settings; - }); + const settings = result.values + .map((row) => { + const setting = parseJsonField(row, {}) as Settings; + return { + id: setting.id, + accountDid: setting.accountDid || null, + activeDid: setting.activeDid || null, + apiServer: setting.apiServer || "", + filterFeedByNearby: setting.filterFeedByNearby || false, + filterFeedByVisible: setting.filterFeedByVisible || false, + finishedOnboarding: setting.finishedOnboarding || false, + firstName: setting.firstName || "", + hideRegisterPromptOnNewContact: + setting.hideRegisterPromptOnNewContact || false, + isRegistered: setting.isRegistered || false, + lastName: setting.lastName || "", + lastAckedOfferToUserJwtId: setting.lastAckedOfferToUserJwtId || "", + lastAckedOfferToUserProjectsJwtId: + setting.lastAckedOfferToUserProjectsJwtId || "", + lastNotifiedClaimId: setting.lastNotifiedClaimId || "", + lastViewedClaimId: setting.lastViewedClaimId || "", + notifyingNewActivityTime: setting.notifyingNewActivityTime || "", + notifyingReminderMessage: setting.notifyingReminderMessage || "", + notifyingReminderTime: setting.notifyingReminderTime || "", + partnerApiServer: setting.partnerApiServer || "", + passkeyExpirationMinutes: setting.passkeyExpirationMinutes, + profileImageUrl: setting.profileImageUrl || "", + searchBoxes: parseJsonField(setting.searchBoxes, []), + showContactGivesInline: setting.showContactGivesInline || false, + showGeneralAdvanced: setting.showGeneralAdvanced || false, + showShortcutBvc: setting.showShortcutBvc || false, + vapid: setting.vapid || "", + warnIfProdServer: setting.warnIfProdServer || false, + warnIfTestServer: setting.warnIfTestServer || false, + webPushServer: setting.webPushServer || "", + } as Settings; + }) + .filter((setting) => { + // Only include settings that have either accountDid or activeDid set + return setting.accountDid || setting.activeDid; + }); logger.info( `[MigrationService] Retrieved ${settings.length} settings from SQLite`, @@ -837,95 +842,79 @@ function accountsEqual(account1: Account, account2: Account): boolean { */ export function generateComparisonYaml(comparison: DataComparison): string { const yaml = { - comparison: { - summary: { - dexieContacts: comparison.dexieContacts.length, - sqliteContacts: comparison.sqliteContacts.length, - dexieSettings: comparison.dexieSettings.length, - sqliteSettings: comparison.sqliteSettings.length, - dexieAccounts: comparison.dexieAccounts.length, - sqliteAccounts: comparison.sqliteAccounts.length, + summary: { + dexieContacts: comparison.dexieContacts.length, + sqliteContacts: comparison.sqliteContacts.filter(c => c.did).length, + dexieSettings: comparison.dexieSettings.length, + sqliteSettings: comparison.sqliteSettings.filter(s => s.accountDid || s.activeDid).length, + dexieAccounts: comparison.dexieAccounts.length, + sqliteAccounts: comparison.sqliteAccounts.filter(a => a.did).length, + }, + differences: { + contacts: { + added: comparison.differences.contacts.added.length, + modified: comparison.differences.contacts.modified.length, + missing: comparison.differences.contacts.missing.filter(c => c.did).length, }, - differences: { - contacts: { - added: comparison.differences.contacts.added.length, - modified: comparison.differences.contacts.modified.length, - missing: comparison.differences.contacts.missing.length, - }, - settings: { - added: comparison.differences.settings.added.length, - modified: comparison.differences.settings.modified.length, - missing: comparison.differences.settings.missing.length, - }, - accounts: { - added: comparison.differences.accounts.added.length, - modified: comparison.differences.accounts.modified.length, - missing: comparison.differences.accounts.missing.length, - }, + settings: { + added: comparison.differences.settings.added.length, + modified: comparison.differences.settings.modified.length, + missing: comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid).length, + }, + accounts: { + added: comparison.differences.accounts.added.length, + modified: comparison.differences.accounts.modified.length, + missing: comparison.differences.accounts.missing.filter(a => a.did).length, }, + }, + details: { contacts: { dexie: comparison.dexieContacts.map((c) => ({ did: c.did, - name: c.name, - notes: c.notes, - profileImageUrl: c.profileImageUrl, - seesMe: c.seesMe, - registered: c.registered, - contactMethods: c.contactMethods, - })), - sqlite: comparison.sqliteContacts.map((c) => ({ - did: c.did, - name: c.name, - notes: c.notes, - profileImageUrl: c.profileImageUrl, - seesMe: c.seesMe, - registered: c.registered, - contactMethods: c.contactMethods, + name: c.name || '', + contactMethods: (c.contactMethods || []).length, })), + sqlite: comparison.sqliteContacts + .filter(c => c.did) + .map((c) => ({ + did: c.did, + name: c.name || '', + contactMethods: (c.contactMethods || []).length, + })), }, settings: { dexie: comparison.dexieSettings.map((s) => ({ id: s.id, - accountDid: s.accountDid, - activeDid: s.activeDid, - firstName: s.firstName, - isRegistered: s.isRegistered, - profileImageUrl: s.profileImageUrl, - showShortcutBvc: s.showShortcutBvc, - searchBoxes: s.searchBoxes, - })), - sqlite: comparison.sqliteSettings.map((s) => ({ - id: s.id, - accountDid: s.accountDid, - activeDid: s.activeDid, - firstName: s.firstName, - isRegistered: s.isRegistered, - profileImageUrl: s.profileImageUrl, - showShortcutBvc: s.showShortcutBvc, - searchBoxes: s.searchBoxes, + type: s.id === MASTER_SETTINGS_KEY ? 'master' : 'account', + did: s.activeDid || s.accountDid, + isRegistered: s.isRegistered || false, })), + sqlite: comparison.sqliteSettings + .filter(s => s.accountDid || s.activeDid) + .map((s) => ({ + id: s.id, + type: s.id === MASTER_SETTINGS_KEY ? 'master' : 'account', + did: s.activeDid || s.accountDid, + isRegistered: s.isRegistered || false, + })), }, accounts: { dexie: comparison.dexieAccounts.map((a) => ({ id: a.id, - dateCreated: a.dateCreated, - derivationPath: a.derivationPath, did: a.did, - identity: a.identity, - mnemonic: a.mnemonic, - passkeyCredIdHex: a.passkeyCredIdHex, - publicKeyHex: a.publicKeyHex, - })), - sqlite: comparison.sqliteAccounts.map((a) => ({ - id: a.id, dateCreated: a.dateCreated, - derivationPath: a.derivationPath, - did: a.did, - identity: a.identity, - mnemonic: a.mnemonic, - passkeyCredIdHex: a.passkeyCredIdHex, - publicKeyHex: a.publicKeyHex, + hasIdentity: !!a.identity, + hasMnemonic: !!a.mnemonic, })), + sqlite: comparison.sqliteAccounts + .filter(a => a.did) + .map((a) => ({ + id: a.id, + did: a.did, + dateCreated: a.dateCreated, + hasIdentity: !!a.identity, + hasMnemonic: !!a.mnemonic, + })), }, }, }; @@ -1111,29 +1100,45 @@ export async function migrateSettings( [dexieSetting.id], ); + // Prepare the data object, handling DIDs based on whether this is the master settings + const settingData: Record = {}; + fieldsToMigrate.forEach((field) => { + if (dexieSetting[field as keyof Settings] !== undefined) { + settingData[field] = dexieSetting[field as keyof Settings]; + } + }); + + // Handle DIDs based on whether this is the master settings + if (dexieSetting.id === MASTER_SETTINGS_KEY) { + // Master settings should only use activeDid + if (dexieSetting.activeDid) { + settingData.activeDid = dexieSetting.activeDid; + } + // Ensure accountDid is null for master settings + settingData.accountDid = null; + } else { + // Non-master settings should only use accountDid + if (dexieSetting.accountDid) { + settingData.accountDid = dexieSetting.accountDid; + } + // Ensure activeDid is null for non-master settings + settingData.activeDid = null; + } + if (existingResult?.values?.length) { if (overwriteExisting) { - // Update existing setting with only the specified fields - const updateData: Record = {}; - fieldsToMigrate.forEach((field) => { - if (dexieSetting[field as keyof Settings] !== undefined) { - updateData[field] = dexieSetting[field as keyof Settings]; - } - }); - - if (Object.keys(updateData).length > 0) { - const { sql, params } = generateUpdateStatement( - updateData as unknown as Record, - "settings", - "id = ?", - [dexieSetting.id], - ); - await platformService.dbExec(sql, params); - result.settingsMigrated++; - logger.info( - `[MigrationService] Updated settings: ${dexieSetting.id}`, - ); - } + // Update existing setting + const { sql, params } = generateUpdateStatement( + settingData, + "settings", + "id = ?", + [dexieSetting.id], + ); + await platformService.dbExec(sql, params); + result.settingsMigrated++; + logger.info( + `[MigrationService] Updated settings: ${dexieSetting.id}`, + ); } else { result.warnings.push( `Settings ${dexieSetting.id} already exists, skipping`, @@ -1141,8 +1146,9 @@ export async function migrateSettings( } } else { // Insert new setting + settingData.id = dexieSetting.id; const { sql, params } = generateInsertStatement( - dexieSetting as unknown as Record, + settingData, "settings", ); await platformService.dbExec(sql, params); diff --git a/src/views/DatabaseMigration.vue b/src/views/DatabaseMigration.vue index 6d2144a1..0265fe7c 100644 --- a/src/views/DatabaseMigration.vue +++ b/src/views/DatabaseMigration.vue @@ -440,17 +440,59 @@ class="mt-4" >

- Added Contacts: + Added Contacts ({{ comparison.differences.contacts.added.length }}):

-
+
- {{ contact.name || "Unnamed" }} ({{ - contact.did.substring(0, 20) - }}...) +
{{ contact.name || '' }}
+
{{ contact.did }}
+
{{ contact.contactMethods?.length || 0 }} contact methods
+
+
+
+ + +
+

+ Modified Contacts ({{ comparison.differences.contacts.modified.length }}): +

+
+
+
{{ contact.name || '' }}
+
{{ contact.did }}
+
{{ contact.contactMethods?.length || 0 }} contact methods
+
+
+
+ + +
+

+ Missing Contacts ({{ comparison.differences.contacts.missing.filter(c => c.did).length }}): +

+
+
+
{{ contact.name || '' }}
+
{{ contact.did }}
+
{{ contact.contactMethods?.length || 0 }} contact methods
@@ -521,15 +563,59 @@ class="mt-4" >

- Added Settings: + Added Settings ({{ comparison.differences.settings.added.length }}):

-
+
- ID: {{ setting.id }} - {{ setting.firstName || "Unnamed" }} +
ID: {{ setting.id }} ({{ setting.id === 1 ? 'master' : 'account' }})
+
{{ setting.activeDid || setting.accountDid }}
+
Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}
+
+
+
+ + +
+

+ Modified Settings ({{ comparison.differences.settings.modified.length }}): +

+
+
+
ID: {{ setting.id }} ({{ setting.id === 1 ? 'master' : 'account' }})
+
{{ setting.activeDid || setting.accountDid }}
+
Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}
+
+
+
+ + +
+

+ Missing Settings ({{ comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid).length }}): +

+
+
+
ID: {{ setting.id }} ({{ setting.id === 1 ? 'master' : 'account' }})
+
{{ setting.activeDid || setting.accountDid }}
+
Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}
@@ -600,15 +686,65 @@ class="mt-4" >

- Added Accounts: + Added Accounts ({{ comparison.differences.accounts.added.length }}):

-
+
- ID: {{ account.id }} - {{ account.did.substring(0, 20) }}... +
ID: {{ account.id }}
+
{{ account.did }}
+
Created: {{ account.dateCreated }}
+
Has Identity: {{ account.identity ? 'Yes' : 'No' }}
+
Has Mnemonic: {{ account.mnemonic ? 'Yes' : 'No' }}
+
+
+
+ + +
+

+ Modified Accounts ({{ comparison.differences.accounts.modified.length }}): +

+
+
+
ID: {{ account.id }}
+
{{ account.did }}
+
Created: {{ account.dateCreated }}
+
Has Identity: {{ account.identity ? 'Yes' : 'No' }}
+
Has Mnemonic: {{ account.mnemonic ? 'Yes' : 'No' }}
+
+
+
+ + +
+

+ Missing Accounts ({{ comparison.differences.accounts.missing.filter(a => a.did).length }}): +

+
+
+
ID: {{ account.id }}
+
{{ account.did }}
+
Created: {{ account.dateCreated }}
+
Has Identity: {{ account.identity ? 'Yes' : 'No' }}
+
Has Mnemonic: {{ account.mnemonic ? 'Yes' : 'No' }}
@@ -746,6 +882,9 @@ export default class DatabaseMigration extends Vue { "[DatabaseMigration] Complete migration successful", result, ); + + // Refresh comparison data after successful migration + this.comparison = await compareDatabases(); } else { this.error = `Migration failed: ${result.errors.join(", ")}`; logger.error( @@ -819,6 +958,9 @@ export default class DatabaseMigration extends Vue { "[DatabaseMigration] Contact migration completed successfully", result, ); + + // Refresh comparison data after successful migration + this.comparison = await compareDatabases(); } else { this.error = `Migration failed: ${result.errors.join(", ")}`; logger.error( @@ -861,6 +1003,9 @@ export default class DatabaseMigration extends Vue { "[DatabaseMigration] Settings migration completed successfully", result, ); + + // Refresh comparison data after successful migration + this.comparison = await compareDatabases(); } else { this.error = `Migration failed: ${result.errors.join(", ")}`; logger.error( @@ -904,6 +1049,9 @@ export default class DatabaseMigration extends Vue { "[DatabaseMigration] Account migration completed successfully", result, ); + + // Refresh comparison data after successful migration + this.comparison = await compareDatabases(); } else { this.error = `Migration failed: ${result.errors.join(", ")}`; logger.error( @@ -937,32 +1085,69 @@ export default class DatabaseMigration extends Vue { try { const newComparison = await compareDatabases(); - // Check if there are any remaining differences - const totalRemaining = - newComparison.differences.contacts.added.length + - newComparison.differences.contacts.modified.length + - newComparison.differences.contacts.missing.length + - newComparison.differences.settings.added.length + - newComparison.differences.settings.modified.length + - newComparison.differences.settings.missing.length + - newComparison.differences.accounts.added.length + - newComparison.differences.accounts.modified.length + - newComparison.differences.accounts.missing.length; + // Calculate differences by type for each table + const differences = { + contacts: { + added: newComparison.differences.contacts.added.length, + modified: newComparison.differences.contacts.modified.length, + missing: newComparison.differences.contacts.missing.length, + }, + settings: { + added: newComparison.differences.settings.added.length, + modified: newComparison.differences.settings.modified.length, + missing: newComparison.differences.settings.missing.length, + }, + accounts: { + added: newComparison.differences.accounts.added.length, + modified: newComparison.differences.accounts.modified.length, + missing: newComparison.differences.accounts.missing.length, + }, + }; + + const totalRemaining = Object.values(differences).reduce( + (sum, table) => + sum + table.added + table.modified + table.missing, + 0 + ); + + // Build a detailed message + const detailMessages = []; + + if (differences.contacts.added + differences.contacts.modified + differences.contacts.missing > 0) { + detailMessages.push( + `Contacts: ${differences.contacts.added} to add, ${differences.contacts.modified} modified, ${differences.contacts.missing} missing` + ); + } + + if (differences.settings.added + differences.settings.modified + differences.settings.missing > 0) { + detailMessages.push( + `Settings: ${differences.settings.added} to add, ${differences.settings.modified} modified, ${differences.settings.missing} missing` + ); + } + + if (differences.accounts.added + differences.accounts.modified + differences.accounts.missing > 0) { + detailMessages.push( + `Accounts: ${differences.accounts.added} to add, ${differences.accounts.modified} modified, ${differences.accounts.missing} missing` + ); + } if (totalRemaining === 0) { this.successMessage = "✅ Migration verification successful! All data has been migrated correctly."; logger.info( - "[DatabaseMigration] Migration verification successful - no differences found", + "[DatabaseMigration] Migration verification successful - no differences found" ); } else { - this.successMessage = `⚠️ Migration verification completed. Found ${totalRemaining} remaining differences. Consider running additional migrations if needed.`; + this.successMessage = `⚠️ Migration verification completed. Found ${totalRemaining} remaining differences:\n${detailMessages.join("\n")}`; + if (differences.settings.modified > 0 || differences.settings.missing > 0) { + this.successMessage += "\n\nNote: Some settings differences may be expected due to default values in SQLite."; + } logger.warn( "[DatabaseMigration] Migration verification found remaining differences", { remaining: totalRemaining, - differences: newComparison.differences, - }, + differences: differences, + } ); }