feat: improve database migration comparison and UI display
- Enhanced migration service to filter empty/default records from comparisons - Updated comparison output to exclude records without meaningful data - Improved UI display with better visual hierarchy and count indicators - Added security improvements to prevent sensitive data exposure - Fixed settings migration to properly handle MASTER_SETTINGS_KEY vs account DIDs - Updated verification display to show filtered counts and detailed differences - Improved data formatting for contacts, settings, and accounts sections - Added proper filtering for missing records to avoid counting empty entries Changes: - Filter SQLite records to only include those with actual DIDs/data - Update comparison counts to reflect meaningful differences only - Enhance UI with count indicators and better visual organization - Replace sensitive data display with boolean flags (hasIdentity, hasMnemonic) - Fix settings migration logic for proper DID field handling - Improve verification message to show detailed breakdown by type - Add proper filtering for missing records in all data types Security: Prevents exposure of mnemonics, private keys, and identity data UI/UX: Cleaner display with better information hierarchy and counts Migration: More accurate comparison results and better debugging visibility
This commit is contained in:
@@ -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<Settings[]> {
|
||||
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 || '<empty>',
|
||||
contactMethods: (c.contactMethods || []).length,
|
||||
})),
|
||||
sqlite: comparison.sqliteContacts
|
||||
.filter(c => c.did)
|
||||
.map((c) => ({
|
||||
did: c.did,
|
||||
name: c.name || '<empty>',
|
||||
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<string, unknown> = {};
|
||||
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<string, unknown> = {};
|
||||
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<string, unknown>,
|
||||
"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<string, unknown>,
|
||||
settingData,
|
||||
"settings",
|
||||
);
|
||||
await platformService.dbExec(sql, params);
|
||||
|
||||
Reference in New Issue
Block a user