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.
This commit is contained in:
Matthew Raymer
2025-06-19 06:13:25 +00:00
parent 51ce2bae9c
commit ec259a7c41
2 changed files with 636 additions and 11 deletions

View File

@@ -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
*