Browse Source

feat: integrate importFromMnemonic utility into migration service and UI

- Add account migration support to migrationService with importFromMnemonic integration
- Extend DataComparison and MigrationResult interfaces to include accounts
- Add getDexieAccounts() and getSqliteAccounts() functions for account retrieval
- Implement compareAccounts() and migrateAccounts() functions with proper error handling
- Update DatabaseMigration.vue UI to support account migration:
  - Add "Migrate Accounts" button with lock icon
  - Extend summary cards grid to show Dexie/SQLite account counts
  - Add Account Differences section with added/modified/missing indicators
  - Update success message to include account migration counts
  - Enhance grid layouts to accommodate 6 summary cards and 3 difference sections

The migration service now provides complete data migration capabilities
for contacts, settings, and accounts, with enhanced reliability through
the importFromMnemonic utility for mnemonic-based account handling.
migrate-dexie-to-sqlite
Matthew Raymer 7 days ago
parent
commit
8a7f142cb7
  1. 406
      src/services/migrationService.ts
  2. 241
      src/views/DatabaseMigration.vue

406
src/services/migrationService.ts

@ -22,12 +22,14 @@
*/
import { PlatformServiceFactory } from "./PlatformServiceFactory";
import { db } from "../db/index";
import { db, accountsDBPromise } from "../db/index";
import { Contact, ContactMethod } from "../db/tables/contacts";
import { Settings } from "../db/tables/settings";
import { Account } from "../db/tables/accounts";
import { logger } from "../utils/logger";
import { parseJsonField } from "../db/databaseUtil";
import { USE_DEXIE_DB } from "../constants/app";
import { importFromMnemonic } from "../libs/util";
/**
* Interface for data comparison results between Dexie and SQLite databases
@ -41,6 +43,8 @@ import { USE_DEXIE_DB } from "../constants/app";
* @property {Contact[]} sqliteContacts - All contacts from SQLite database
* @property {Settings[]} dexieSettings - All settings from Dexie database
* @property {Settings[]} sqliteSettings - All settings from SQLite database
* @property {Account[]} dexieAccounts - All accounts from Dexie database
* @property {Account[]} sqliteAccounts - All accounts from SQLite database
* @property {Object} differences - Detailed differences between databases
* @property {Object} differences.contacts - Contact-specific differences
* @property {Contact[]} differences.contacts.added - Contacts in Dexie but not SQLite
@ -50,12 +54,18 @@ import { USE_DEXIE_DB } from "../constants/app";
* @property {Settings[]} differences.settings.added - Settings in Dexie but not SQLite
* @property {Settings[]} differences.settings.modified - Settings that differ between databases
* @property {Settings[]} differences.settings.missing - Settings in SQLite but not Dexie
* @property {Object} differences.accounts - Account-specific differences
* @property {Account[]} differences.accounts.added - Accounts in Dexie but not SQLite
* @property {Account[]} differences.accounts.modified - Accounts that differ between databases
* @property {Account[]} differences.accounts.missing - Accounts in SQLite but not Dexie
*/
export interface DataComparison {
dexieContacts: Contact[];
sqliteContacts: Contact[];
dexieSettings: Settings[];
sqliteSettings: Settings[];
dexieAccounts: Account[];
sqliteAccounts: Account[];
differences: {
contacts: {
added: Contact[];
@ -67,6 +77,11 @@ export interface DataComparison {
modified: Settings[];
missing: Settings[];
};
accounts: {
added: Account[];
modified: Account[];
missing: Account[];
};
};
}
@ -81,6 +96,7 @@ export interface DataComparison {
* @property {boolean} success - Whether the migration operation completed successfully
* @property {number} contactsMigrated - Number of contacts successfully migrated
* @property {number} settingsMigrated - Number of settings successfully migrated
* @property {number} accountsMigrated - Number of accounts successfully migrated
* @property {string[]} errors - Array of error messages encountered during migration
* @property {string[]} warnings - Array of warning messages (non-fatal issues)
*/
@ -88,6 +104,7 @@ export interface MigrationResult {
success: boolean;
contactsMigrated: number;
settingsMigrated: number;
accountsMigrated: number;
errors: string[];
warnings: string[];
}
@ -315,6 +332,105 @@ export async function getSqliteSettings(): Promise<Settings[]> {
}
}
/**
* Retrieves all accounts from the SQLite database
*
* This function uses the platform service to query the SQLite database
* and retrieve all account records. It handles the conversion of raw
* database results into properly typed Account objects.
*
* The function also handles JSON parsing for complex fields like
* identity, ensuring proper type conversion.
*
* @async
* @function getSqliteAccounts
* @returns {Promise<Account[]>} Array of all accounts from SQLite database
* @throws {Error} If database query fails or data conversion fails
* @example
* ```typescript
* try {
* const accounts = await getSqliteAccounts();
* console.log(`Retrieved ${accounts.length} accounts from SQLite`);
* } catch (error) {
* console.error('Failed to retrieve SQLite accounts:', error);
* }
* ```
*/
export async function getSqliteAccounts(): Promise<Account[]> {
try {
const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery("SELECT * FROM accounts");
if (!result?.values?.length) {
return [];
}
const accounts = result.values.map((row) => {
const account = parseJsonField(row, {}) as any;
return {
id: account.id,
dateCreated: account.dateCreated || "",
derivationPath: account.derivationPath || "",
did: account.did || "",
identity: account.identity || "",
mnemonic: account.mnemonic || "",
passkeyCredIdHex: account.passkeyCredIdHex || "",
publicKeyHex: account.publicKeyHex || "",
} as Account;
});
logger.info(
`[MigrationService] Retrieved ${accounts.length} accounts from SQLite`,
);
return accounts;
} catch (error) {
logger.error("[MigrationService] Error retrieving SQLite accounts:", error);
throw new Error(`Failed to retrieve SQLite accounts: ${error}`);
}
}
/**
* Retrieves all accounts from the Dexie (IndexedDB) database
*
* This function connects to the Dexie database and retrieves all account
* records. It requires that USE_DEXIE_DB is enabled in the app constants.
*
* The function handles database opening and error conditions, providing
* detailed logging for debugging purposes.
*
* @async
* @function getDexieAccounts
* @returns {Promise<Account[]>} Array of all accounts from Dexie database
* @throws {Error} If Dexie database is not enabled or if database access fails
* @example
* ```typescript
* try {
* const accounts = await getDexieAccounts();
* console.log(`Retrieved ${accounts.length} accounts from Dexie`);
* } catch (error) {
* console.error('Failed to retrieve Dexie accounts:', error);
* }
* ```
*/
export async function getDexieAccounts(): Promise<Account[]> {
if (!USE_DEXIE_DB) {
throw new Error("Dexie database is not enabled");
}
try {
const accountsDB = await accountsDBPromise;
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
logger.info(
`[MigrationService] Retrieved ${accounts.length} accounts from Dexie`,
);
return accounts;
} catch (error) {
logger.error("[MigrationService] Error retrieving Dexie accounts:", error);
throw new Error(`Failed to retrieve Dexie accounts: ${error}`);
}
}
/**
* Compares data between Dexie and SQLite databases
*
@ -346,13 +462,21 @@ export async function getSqliteSettings(): Promise<Settings[]> {
export async function compareDatabases(): Promise<DataComparison> {
logger.info("[MigrationService] Starting database comparison");
const [dexieContacts, sqliteContacts, dexieSettings, sqliteSettings] =
await Promise.all([
getDexieContacts(),
getSqliteContacts(),
getDexieSettings(),
getSqliteSettings(),
]);
const [
dexieContacts,
sqliteContacts,
dexieSettings,
sqliteSettings,
dexieAccounts,
sqliteAccounts,
] = await Promise.all([
getDexieContacts(),
getSqliteContacts(),
getDexieSettings(),
getSqliteSettings(),
getDexieAccounts(),
getSqliteAccounts(),
]);
// Compare contacts
const contactDifferences = compareContacts(dexieContacts, sqliteContacts);
@ -360,14 +484,20 @@ export async function compareDatabases(): Promise<DataComparison> {
// Compare settings
const settingsDifferences = compareSettings(dexieSettings, sqliteSettings);
// Compare accounts
const accountDifferences = compareAccounts(dexieAccounts, sqliteAccounts);
const comparison: DataComparison = {
dexieContacts,
sqliteContacts,
dexieSettings,
sqliteSettings,
dexieAccounts,
sqliteAccounts,
differences: {
contacts: contactDifferences,
settings: settingsDifferences,
accounts: accountDifferences,
},
};
@ -376,8 +506,11 @@ export async function compareDatabases(): Promise<DataComparison> {
sqliteContacts: sqliteContacts.length,
dexieSettings: dexieSettings.length,
sqliteSettings: sqliteSettings.length,
dexieAccounts: dexieAccounts.length,
sqliteAccounts: sqliteAccounts.length,
contactDifferences: contactDifferences,
settingsDifferences: settingsDifferences,
accountDifferences: accountDifferences,
});
return comparison;
@ -491,6 +624,57 @@ function compareSettings(
return { added, modified, missing };
}
/**
* Compares accounts between Dexie and SQLite databases
*
* This helper function analyzes two arrays of accounts and identifies
* which accounts are added (in Dexie but not SQLite), modified
* (different between databases), or missing (in SQLite but not Dexie).
*
* The comparison is based on the account's ID as the primary key,
* with detailed field-by-field comparison for modified accounts.
*
* @function compareAccounts
* @param {Account[]} dexieAccounts - Accounts from Dexie database
* @param {Account[]} sqliteAccounts - Accounts from SQLite database
* @returns {Object} Object containing added, modified, and missing accounts
* @returns {Account[]} returns.added - Accounts in Dexie but not SQLite
* @returns {Account[]} returns.modified - Accounts that differ between databases
* @returns {Account[]} returns.missing - Accounts in SQLite but not Dexie
* @example
* ```typescript
* const differences = compareAccounts(dexieAccounts, sqliteAccounts);
* console.log(`Added: ${differences.added.length}`);
* console.log(`Modified: ${differences.modified.length}`);
* console.log(`Missing: ${differences.missing.length}`);
* ```
*/
function compareAccounts(dexieAccounts: Account[], sqliteAccounts: Account[]) {
const added: Account[] = [];
const modified: Account[] = [];
const missing: Account[] = [];
// Find accounts that exist in Dexie but not in SQLite
for (const dexieAccount of dexieAccounts) {
const sqliteAccount = sqliteAccounts.find((a) => a.id === dexieAccount.id);
if (!sqliteAccount) {
added.push(dexieAccount);
} else if (!accountsEqual(dexieAccount, sqliteAccount)) {
modified.push(dexieAccount);
}
}
// Find accounts that exist in SQLite but not in Dexie
for (const sqliteAccount of sqliteAccounts) {
const dexieAccount = dexieAccounts.find((a) => a.id === sqliteAccount.id);
if (!dexieAccount) {
missing.push(sqliteAccount);
}
}
return { added, modified, missing };
}
/**
* Compares two contacts for equality
*
@ -592,6 +776,43 @@ function settingsEqual(settings1: Settings, settings2: Settings): boolean {
);
}
/**
* Compares two accounts for equality
*
* This helper function performs a deep comparison of two Account objects
* to determine if they are identical. The comparison includes all
* relevant fields including complex objects like identity.
*
* For identity, the function uses JSON.stringify to compare
* the objects, ensuring that both structure and content are identical.
*
* @function accountsEqual
* @param {Account} account1 - First account to compare
* @param {Account} account2 - Second account to compare
* @returns {boolean} True if accounts are identical, false otherwise
* @example
* ```typescript
* const areEqual = accountsEqual(account1, account2);
* if (areEqual) {
* console.log('Accounts are identical');
* } else {
* console.log('Accounts differ');
* }
* ```
*/
function accountsEqual(account1: Account, account2: Account): boolean {
return (
account1.id === account2.id &&
account1.dateCreated === account2.dateCreated &&
account1.derivationPath === account2.derivationPath &&
account1.did === account2.did &&
account1.identity === account2.identity &&
account1.mnemonic === account2.mnemonic &&
account1.passkeyCredIdHex === account2.passkeyCredIdHex &&
account1.publicKeyHex === account2.publicKeyHex
);
}
/**
* Generates YAML-formatted comparison data
*
@ -622,6 +843,8 @@ export function generateComparisonYaml(comparison: DataComparison): string {
sqliteContacts: comparison.sqliteContacts.length,
dexieSettings: comparison.dexieSettings.length,
sqliteSettings: comparison.sqliteSettings.length,
dexieAccounts: comparison.dexieAccounts.length,
sqliteAccounts: comparison.sqliteAccounts.length,
},
differences: {
contacts: {
@ -634,6 +857,11 @@ export function generateComparisonYaml(comparison: DataComparison): string {
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,
},
},
contacts: {
dexie: comparison.dexieContacts.map((c) => ({
@ -677,6 +905,28 @@ export function generateComparisonYaml(comparison: DataComparison): string {
searchBoxes: s.searchBoxes,
})),
},
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,
})),
},
},
};
@ -725,6 +975,7 @@ export async function migrateContacts(
success: true,
contactsMigrated: 0,
settingsMigrated: 0,
accountsMigrated: 0,
errors: [],
warnings: [],
};
@ -834,6 +1085,7 @@ export async function migrateSettings(
success: true,
contactsMigrated: 0,
settingsMigrated: 0,
accountsMigrated: 0,
errors: [],
warnings: [],
};
@ -921,6 +1173,144 @@ export async function migrateSettings(
}
}
/**
* Migrates accounts from Dexie to SQLite database
*
* This function transfers all accounts from the Dexie database to the
* SQLite database. It handles both new accounts (INSERT) and existing
* accounts (UPDATE) based on the overwriteExisting parameter.
*
* For accounts with mnemonic data, the function uses importFromMnemonic
* to ensure proper key derivation and identity creation during migration.
*
* The function processes accounts one by one to ensure data integrity
* and provides detailed logging of the migration process. It returns
* comprehensive results including success status, counts, and any
* errors or warnings encountered.
*
* @async
* @function migrateAccounts
* @param {boolean} [overwriteExisting=false] - Whether to overwrite existing accounts in SQLite
* @returns {Promise<MigrationResult>} Detailed results of the migration operation
* @throws {Error} If the migration process fails completely
* @example
* ```typescript
* try {
* const result = await migrateAccounts(true); // Overwrite existing
* if (result.success) {
* console.log(`Successfully migrated ${result.accountsMigrated} accounts`);
* } else {
* console.error('Migration failed:', result.errors);
* }
* } catch (error) {
* console.error('Migration process failed:', error);
* }
* ```
*/
export async function migrateAccounts(
overwriteExisting: boolean = false,
): Promise<MigrationResult> {
logger.info("[MigrationService] Starting account migration", {
overwriteExisting,
});
const result: MigrationResult = {
success: true,
contactsMigrated: 0,
settingsMigrated: 0,
accountsMigrated: 0,
errors: [],
warnings: [],
};
try {
const dexieAccounts = await getDexieAccounts();
const platformService = PlatformServiceFactory.getInstance();
for (const account of dexieAccounts) {
try {
// Check if account already exists
const existingResult = await platformService.dbQuery(
"SELECT id FROM accounts WHERE id = ?",
[account.id],
);
if (existingResult?.values?.length) {
if (overwriteExisting) {
// Update existing account
const { sql, params } = generateUpdateStatement(
account as unknown as Record<string, unknown>,
"accounts",
"id = ?",
[account.id],
);
await platformService.dbExec(sql, params);
result.accountsMigrated++;
logger.info(`[MigrationService] Updated account: ${account.id}`);
} else {
result.warnings.push(
`Account ${account.id} already exists, skipping`,
);
}
} else {
// For new accounts with mnemonic, use importFromMnemonic for proper key derivation
if (account.mnemonic && account.derivationPath) {
try {
// Use importFromMnemonic to ensure proper key derivation and identity creation
await importFromMnemonic(
account.mnemonic,
account.derivationPath,
false, // Don't erase existing accounts during migration
);
logger.info(
`[MigrationService] Imported account with mnemonic: ${account.id}`,
);
} catch (importError) {
// Fall back to direct insertion if importFromMnemonic fails
logger.warn(
`[MigrationService] importFromMnemonic failed for account ${account.id}, falling back to direct insertion: ${importError}`,
);
const { sql, params } = generateInsertStatement(
account as unknown as Record<string, unknown>,
"accounts",
);
await platformService.dbExec(sql, params);
}
} else {
// Insert new account without mnemonic
const { sql, params } = generateInsertStatement(
account as unknown as Record<string, unknown>,
"accounts",
);
await platformService.dbExec(sql, params);
}
result.accountsMigrated++;
logger.info(`[MigrationService] Added account: ${account.id}`);
}
} catch (error) {
const errorMsg = `Failed to migrate account ${account.id}: ${error}`;
logger.error("[MigrationService]", errorMsg);
result.errors.push(errorMsg);
result.success = false;
}
}
logger.info("[MigrationService] Account migration completed", {
accountsMigrated: result.accountsMigrated,
errors: result.errors.length,
warnings: result.warnings.length,
});
return result;
} catch (error) {
const errorMsg = `Account migration failed: ${error}`;
logger.error("[MigrationService]", errorMsg);
result.errors.push(errorMsg);
result.success = false;
return result;
}
}
/**
* Generates SQL INSERT statement and parameters from a model object
*

241
src/views/DatabaseMigration.vue

@ -140,6 +140,27 @@
Migrate Settings
</button>
<button
:disabled="isLoading || !isDexieEnabled || !comparison"
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-orange-600 hover:bg-orange-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-orange-500 disabled:opacity-50 disabled:cursor-not-allowed"
@click="migrateAccounts"
>
<svg
class="-ml-1 mr-3 h-5 w-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
Migrate Accounts
</button>
<button
:disabled="!comparison"
class="inline-flex items-center px-4 py-2 border border-gray-300 text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed"
@ -250,7 +271,7 @@
<!-- Comparison Results -->
<div v-if="comparison" class="space-y-6">
<!-- Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-6 gap-6">
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
@ -384,10 +405,74 @@
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg
class="h-6 w-6 text-orange-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"
/>
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">
Dexie Accounts
</dt>
<dd class="text-lg font-medium text-gray-900">
{{ comparison.dexieAccounts.length }}
</dd>
</dl>
</div>
</div>
</div>
</div>
<div class="bg-white overflow-hidden shadow rounded-lg">
<div class="p-5">
<div class="flex items-center">
<div class="flex-shrink-0">
<svg
class="h-6 w-6 text-teal-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
</div>
<div class="ml-5 w-0 flex-1">
<dl>
<dt class="text-sm font-medium text-gray-500 truncate">
SQLite Accounts
</dt>
<dd class="text-lg font-medium text-gray-900">
{{ comparison.sqliteAccounts.length }}
</dd>
</dl>
</div>
</div>
</div>
</div>
</div>
<!-- Differences Section -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Contacts Differences -->
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
@ -601,6 +686,112 @@
</div>
</div>
</div>
<!-- Account Differences -->
<div class="bg-white shadow rounded-lg">
<div class="px-4 py-5 sm:p-6">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">
Account Differences
</h3>
<div class="space-y-4">
<div
class="flex items-center justify-between p-3 bg-blue-50 rounded-lg"
>
<div class="flex items-center">
<svg
class="h-5 w-5 text-blue-600 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
/>
</svg>
<span class="text-sm font-medium text-blue-900">Added</span>
</div>
<span class="text-sm font-bold text-blue-900">{{
comparison.differences.accounts.added.length
}}</span>
</div>
<div
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<svg
class="h-5 w-5 text-yellow-600 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"
/>
</svg>
<span class="text-sm font-medium text-yellow-900"
>Modified</span
>
</div>
<span class="text-sm font-bold text-yellow-900">{{
comparison.differences.accounts.modified.length
}}</span>
</div>
<div
class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
>
<div class="flex items-center">
<svg
class="h-5 w-5 text-red-600 mr-2"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
<span class="text-sm font-medium text-red-900"
>Missing</span
>
</div>
<span class="text-sm font-bold text-red-900">{{
comparison.differences.accounts.missing.length
}}</span>
</div>
</div>
<!-- Account Details -->
<div
v-if="comparison.differences.accounts.added.length > 0"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Added Accounts:
</h4>
<div class="max-h-32 overflow-y-auto space-y-1">
<div
v-for="account in comparison.differences.accounts.added"
:key="account.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
ID: {{ account.id }} - {{ account.did.substring(0, 20) }}...
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Migration Options -->
@ -645,6 +836,7 @@ import {
compareDatabases,
migrateContacts,
migrateSettings,
migrateAccounts,
generateComparisonYaml,
type DataComparison,
type MigrationResult,
@ -707,7 +899,7 @@ export default class DatabaseMigration extends Vue {
try {
this.comparison = await compareDatabases();
this.successMessage = `Comparison completed successfully. Found ${this.comparison.differences.contacts.added.length + this.comparison.differences.settings.added.length} items to migrate.`;
this.successMessage = `Comparison completed successfully. Found ${this.comparison.differences.contacts.added.length + this.comparison.differences.settings.added.length + this.comparison.differences.accounts.added.length} items to migrate.`;
logger.info(
"[DatabaseMigration] Database comparison completed successfully",
);
@ -803,6 +995,49 @@ export default class DatabaseMigration extends Vue {
}
}
/**
* Migrates accounts from Dexie to SQLite database
*
* This method transfers accounts from the Dexie database to SQLite,
* with options to overwrite existing records. For accounts with mnemonic
* data, it uses the importFromMnemonic utility for proper key derivation.
*
* @async
* @returns {Promise<void>}
*/
async migrateAccounts(): Promise<void> {
this.setLoading("Migrating accounts...");
this.clearMessages();
try {
const result: MigrationResult = await migrateAccounts(
this.overwriteExisting,
);
if (result.success) {
this.successMessage = `Successfully migrated ${result.accountsMigrated} accounts.`;
if (result.warnings.length > 0) {
this.successMessage += ` ${result.warnings.length} warnings.`;
}
logger.info(
"[DatabaseMigration] Account migration completed successfully",
result,
);
} else {
this.error = `Migration failed: ${result.errors.join(", ")}`;
logger.error(
"[DatabaseMigration] Account migration failed:",
result.errors,
);
}
} catch (error) {
this.error = `Failed to migrate accounts: ${error}`;
logger.error("[DatabaseMigration] Account migration failed:", error);
} finally {
this.setLoading("");
}
}
/**
* Exports comparison results to a file
*

Loading…
Cancel
Save