Browse Source

fix linting

migrate-dexie-to-sqlite
Trent Larson 6 days ago
parent
commit
4162208b7f
  1. 2
      src/db/tables/contacts.ts
  2. 197
      src/services/indexedDBMigrationService.ts
  3. 253
      src/views/DatabaseMigration.vue

2
src/db/tables/contacts.ts

@ -21,7 +21,7 @@ export interface Contact {
export type ContactWithJsonStrings = Contact & {
contactMethods?: string;
}
};
export const ContactSchema = {
contacts: "&did, name", // no need to key by other things

197
src/services/indexedDBMigrationService.ts

@ -26,10 +26,19 @@ import "dexie-export-import";
import { PlatformServiceFactory } from "./PlatformServiceFactory";
import { db, accountsDBPromise } from "../db/index";
import { Contact, ContactMethod } from "../db/tables/contacts";
import { Settings, MASTER_SETTINGS_KEY, BoundingBox } from "../db/tables/settings";
import {
Settings,
MASTER_SETTINGS_KEY,
BoundingBox,
} from "../db/tables/settings";
import { Account } from "../db/tables/accounts";
import { logger } from "../utils/logger";
import { mapColumnsToValues, parseJsonField, generateUpdateStatement, generateInsertStatement } from "../db/databaseUtil";
import {
mapColumnsToValues,
parseJsonField,
generateUpdateStatement,
generateInsertStatement,
} from "../db/databaseUtil";
import { importFromMnemonic } from "../libs/util";
/**
@ -186,12 +195,18 @@ export async function getSqliteContacts(): Promise<Contact[]> {
let contacts: Contact[] = [];
if (result?.values?.length) {
const preContacts = mapColumnsToValues(result.columns, result.values) as unknown as Contact[];
const preContacts = mapColumnsToValues(
result.columns,
result.values,
) as unknown as Contact[];
// This is redundant since absurd-sql auto-parses JSON strings to objects.
// But we started it, and it should be known everywhere, so we're keeping it.
contacts = preContacts.map((contact) => {
if (contact.contactMethods) {
contact.contactMethods = parseJsonField(contact.contactMethods, []) as ContactMethod[];
contact.contactMethods = parseJsonField(
contact.contactMethods,
[],
) as ContactMethod[];
}
return contact;
});
@ -212,7 +227,7 @@ export async function getSqliteContacts(): Promise<Contact[]> {
*
* This function connects to the Dexie database and retrieves all settings
* records.
*
*
* Settings include both master settings (id=1) and account-specific settings
* that override the master settings for particular user accounts.
*
@ -275,16 +290,21 @@ export async function getSqliteSettings(): Promise<Settings[]> {
let settings: Settings[] = [];
if (result?.values?.length) {
const presettings =
mapColumnsToValues(result.columns, result.values) as Settings[];
const presettings = mapColumnsToValues(
result.columns,
result.values,
) as Settings[];
// This is redundant since absurd-sql auto-parses JSON strings to objects.
// But we started it, and it should be known everywhere, so we're keeping it.
settings = presettings.map((setting) => {
if (setting.searchBoxes) {
setting.searchBoxes = parseJsonField(setting.searchBoxes, []) as Array<{ name: string, bbox: BoundingBox }>;
setting.searchBoxes = parseJsonField(
setting.searchBoxes,
[],
) as Array<{ name: string; bbox: BoundingBox }>;
}
return setting;
})
});
}
logger.info(
@ -557,7 +577,9 @@ function compareSettings(
// Find settings that exist in Dexie but not in SQLite
for (const dexieSetting of dexieSettings) {
const sqliteSetting = sqliteSettings.find((s) => s.accountDid == dexieSetting.accountDid);
const sqliteSetting = sqliteSettings.find(
(s) => s.accountDid == dexieSetting.accountDid,
);
if (!sqliteSetting) {
added.push(dexieSetting);
} else if (!settingsEqual(dexieSetting, sqliteSetting)) {
@ -569,7 +591,9 @@ function compareSettings(
// Find settings that exist in SQLite but not in Dexie
for (const sqliteSetting of sqliteSettings) {
const dexieSetting = dexieSettings.find((s) => s.accountDid == sqliteSetting.accountDid);
const dexieSetting = dexieSettings.find(
(s) => s.accountDid == sqliteSetting.accountDid,
);
if (!dexieSetting) {
missing.push(sqliteSetting);
}
@ -654,22 +678,29 @@ function compareAccounts(dexieAccounts: Account[], sqliteDids: string[]) {
* ```
*/
function contactsEqual(contact1: Contact, contact2: Contact): boolean {
const ifEmpty = (arg: any, def: any) => !!arg ? arg : def;
const ifEmpty = (arg: any, def: any) => (arg ? arg : def);
const contact1Methods =
contact1.contactMethods && Array.isArray(contact1.contactMethods) && contact1.contactMethods.length > 0
? JSON.stringify(contact1.contactMethods)
: "[]";
contact1.contactMethods &&
Array.isArray(contact1.contactMethods) &&
contact1.contactMethods.length > 0
? JSON.stringify(contact1.contactMethods)
: "[]";
const contact2Methods =
contact2.contactMethods && Array.isArray(contact2.contactMethods) && contact2.contactMethods.length > 0
? JSON.stringify(contact2.contactMethods)
: "[]";
contact2.contactMethods &&
Array.isArray(contact2.contactMethods) &&
contact2.contactMethods.length > 0
? JSON.stringify(contact2.contactMethods)
: "[]";
return (
ifEmpty(contact1.did, "") == ifEmpty(contact2.did, "") &&
ifEmpty(contact1.name, "") == ifEmpty(contact2.name, "") &&
ifEmpty(contact1.notes, "") == ifEmpty(contact2.notes, "") &&
ifEmpty(contact1.profileImageUrl, "") == ifEmpty(contact2.profileImageUrl, "") &&
ifEmpty(contact1.publicKeyBase64, "") == ifEmpty(contact2.publicKeyBase64, "") &&
ifEmpty(contact1.nextPubKeyHashB64, "") == ifEmpty(contact2.nextPubKeyHashB64, "") &&
ifEmpty(contact1.profileImageUrl, "") ==
ifEmpty(contact2.profileImageUrl, "") &&
ifEmpty(contact1.publicKeyBase64, "") ==
ifEmpty(contact2.publicKeyBase64, "") &&
ifEmpty(contact1.nextPubKeyHashB64, "") ==
ifEmpty(contact2.nextPubKeyHashB64, "") &&
!!contact1.seesMe == !!contact2.seesMe &&
!!contact1.registered == !!contact2.registered &&
contact1Methods == contact2Methods
@ -762,18 +793,21 @@ function settingsEqual(settings1: Settings, settings2: Settings): boolean {
* }
* ```
*/
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
);
}
//
// unused
//
// 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
@ -801,58 +835,64 @@ export function generateComparisonYaml(comparison: DataComparison): string {
const yaml = {
summary: {
dexieContacts: comparison.dexieContacts.length,
sqliteContacts: comparison.sqliteContacts.filter(c => c.did).length,
sqliteContacts: comparison.sqliteContacts.filter((c) => c.did).length,
dexieSettings: comparison.dexieSettings.length,
sqliteSettings: comparison.sqliteSettings.filter(s => s.accountDid || s.activeDid).length,
sqliteSettings: comparison.sqliteSettings.filter(
(s) => s.accountDid || s.activeDid,
).length,
dexieAccounts: comparison.dexieAccounts.length,
sqliteAccounts: comparison.sqliteAccounts.filter(a => a).length,
sqliteAccounts: comparison.sqliteAccounts.filter((a) => a).length,
},
differences: {
contacts: {
added: comparison.differences.contacts.added.length,
modified: comparison.differences.contacts.modified.length,
unmodified: comparison.differences.contacts.unmodified.length,
missing: comparison.differences.contacts.missing.filter(c => c.did).length,
missing: comparison.differences.contacts.missing.filter((c) => c.did)
.length,
},
settings: {
added: comparison.differences.settings.added.length,
modified: comparison.differences.settings.modified.length,
unmodified: comparison.differences.settings.unmodified.length,
missing: comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid).length,
missing: comparison.differences.settings.missing.filter(
(s) => s.accountDid || s.activeDid,
).length,
},
accounts: {
added: comparison.differences.accounts.added.length,
unmodified: comparison.differences.accounts.unmodified.length,
missing: comparison.differences.accounts.missing.filter(a => a).length,
missing: comparison.differences.accounts.missing.filter((a) => a)
.length,
},
},
details: {
contacts: {
dexie: comparison.dexieContacts.map((c) => ({
did: c.did,
name: c.name || '<empty>',
name: c.name || "<empty>",
contactMethods: (c.contactMethods || []).length,
})),
sqlite: comparison.sqliteContacts
.filter(c => c.did)
.filter((c) => c.did)
.map((c) => ({
did: c.did,
name: c.name || '<empty>',
name: c.name || "<empty>",
contactMethods: (c.contactMethods || []).length,
})),
},
settings: {
dexie: comparison.dexieSettings.map((s) => ({
id: s.id,
type: s.id === MASTER_SETTINGS_KEY ? 'master' : 'account',
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)
.filter((s) => s.accountDid || s.activeDid)
.map((s) => ({
id: s.id,
type: s.id === MASTER_SETTINGS_KEY ? 'master' : 'account',
type: s.id === MASTER_SETTINGS_KEY ? "master" : "account",
did: s.activeDid || s.accountDid,
isRegistered: s.isRegistered || false,
})),
@ -1039,20 +1079,22 @@ export async function migrateSettings(): Promise<MigrationResult> {
dexieSettings: dexieSettings.length,
});
const platformService = PlatformServiceFactory.getInstance();
// Create an array of promises for all settings migrations
const migrationPromises = dexieSettings.map(async (setting) => {
logger.info("[MigrationService] Starting to migrate settings", setting);
let sqliteSettingRaw: { columns: string[], values: unknown[][] } | undefined;
let sqliteSettingRaw:
| { columns: string[]; values: unknown[][] }
| undefined;
if (!setting.accountDid) {
sqliteSettingRaw = await platformService.dbQuery(
"SELECT * FROM settings WHERE accountDid is null"
"SELECT * FROM settings WHERE accountDid is null",
);
} else {
sqliteSettingRaw = await platformService.dbQuery(
"SELECT * FROM settings WHERE accountDid = ?",
[setting.accountDid]
)
[setting.accountDid],
);
}
logger.info("[MigrationService] Migrating one set of settings:", {
setting,
@ -1060,9 +1102,11 @@ export async function migrateSettings(): Promise<MigrationResult> {
});
if (sqliteSettingRaw?.values?.length) {
// should cover the master settings, were accountDid is null
const sqliteSettings = mapColumnsToValues(sqliteSettingRaw.columns, sqliteSettingRaw.values) as unknown as Settings[];
const sqliteSettings = mapColumnsToValues(
sqliteSettingRaw.columns,
sqliteSettingRaw.values,
) as unknown as Settings[];
const sqliteSetting = sqliteSettings[0];
console.log('sqliteSetting', sqliteSetting)
let conditional: string;
let preparams: unknown[];
if (!setting.accountDid) {
@ -1076,16 +1120,16 @@ export async function migrateSettings(): Promise<MigrationResult> {
sqliteSetting as unknown as Record<string, unknown>,
"settings",
conditional,
preparams
preparams,
);
await platformService.dbExec(sql, params)
await platformService.dbExec(sql, params);
result.settingsMigrated++;
} else {
// insert new setting
delete setting.activeDid; // ensure we don't set the activeDid (since master settings are an update and don't hit this case)
const { sql, params } = generateInsertStatement(
setting as unknown as Record<string, unknown>,
"settings"
"settings",
);
await platformService.dbExec(sql, params);
result.settingsMigrated++;
@ -1094,12 +1138,19 @@ export async function migrateSettings(): Promise<MigrationResult> {
// Wait for all migrations to complete
const updatedSettings = await Promise.all(migrationPromises);
logger.info("[MigrationService] Finished migrating settings", updatedSettings, result);
logger.info(
"[MigrationService] Finished migrating settings",
updatedSettings,
result,
);
return result;
} catch (error) {
logger.error("[MigrationService] Complete settings migration failed:", error);
logger.error(
"[MigrationService] Complete settings migration failed:",
error,
);
const errorMessage = `Settings migration failed: ${error}`;
result.errors.push(errorMessage);
result.success = false;
@ -1158,12 +1209,17 @@ export async function migrateAccounts(): Promise<MigrationResult> {
// Group accounts by DID and keep only the most recent one
const accountsByDid = new Map<string, Account>();
dexieAccounts.forEach(account => {
dexieAccounts.forEach((account) => {
const existingAccount = accountsByDid.get(account.did);
if (!existingAccount || new Date(account.dateCreated) > new Date(existingAccount.dateCreated)) {
if (
!existingAccount ||
new Date(account.dateCreated) > new Date(existingAccount.dateCreated)
) {
accountsByDid.set(account.did, account);
if (existingAccount) {
result.warnings.push(`Found duplicate account for DID ${account.did}, keeping most recent`);
result.warnings.push(
`Found duplicate account for DID ${account.did}, keeping most recent`,
);
}
}
});
@ -1174,30 +1230,34 @@ export async function migrateAccounts(): Promise<MigrationResult> {
// Check if account already exists
const existingResult = await platformService.dbQuery(
"SELECT did FROM accounts WHERE did = ?",
[did]
[did],
);
if (existingResult?.values?.length) {
result.warnings.push(`Account with DID ${did} already exists, skipping`);
result.warnings.push(
`Account with DID ${did} already exists, skipping`,
);
continue;
}
if (account.mnemonic) {
await importFromMnemonic(account.mnemonic, account.derivationPath);
result.accountsMigrated++;
} else {
result.errors.push(`Account with DID ${did} has no mnemonic, skipping`);
result.errors.push(
`Account with DID ${did} has no mnemonic, skipping`,
);
}
logger.info("[MigrationService] Successfully migrated account", {
did,
dateCreated: account.dateCreated
dateCreated: account.dateCreated,
});
} catch (error) {
const errorMessage = `Failed to migrate account ${did}: ${error}`;
result.errors.push(errorMessage);
logger.error("[MigrationService] Account migration failed:", {
error,
did
did,
});
}
}
@ -1211,7 +1271,10 @@ export async function migrateAccounts(): Promise<MigrationResult> {
const errorMessage = `Account migration failed: ${error}`;
result.errors.push(errorMessage);
result.success = false;
logger.error("[MigrationService] Complete account migration failed:", error);
logger.error(
"[MigrationService] Complete account migration failed:",
error,
);
return result;
}
}

253
src/views/DatabaseMigration.vue

@ -9,8 +9,9 @@
databases
</p>
<p class="mt-2 text-gray-600">
First, we recommend you hit these first two buttons to get your backups, just in case!
If anything doesn't look right when you're done, Trent will help with... anything and everything. 😁
First, we recommend you hit these first two buttons to get your
backups, just in case! If anything doesn't look right when you're
done, Trent will help with... anything and everything. 😁
</p>
</div>
@ -50,28 +51,34 @@
</div>
-->
<div
class="text-lg leading-6 font-medium text-red-900 mt-4"
>
<div class="text-lg leading-6 font-medium text-red-900 mt-4">
<p
v-if="comparison && (comparison.sqliteAccounts.length > 1 || comparison.sqliteSettings.length > 1 || comparison.sqliteContacts.length > 0)"
v-if="
comparison &&
(comparison.sqliteAccounts.length > 1 ||
comparison.sqliteSettings.length > 1 ||
comparison.sqliteContacts.length > 0)
"
>
Beware: you have unexpected existing data in the new database that will be overwritten. Talk with Trent.
Beware: you have unexpected existing data in the new database that
will be overwritten. Talk with Trent.
</p>
<p
v-if="cannotfindMainAccount"
>
<p v-if="cannotfindMainAccount">
We cannot find your main account. Talk with Trent.
</p>
<p
v-if="hasMultipleMnemonics"
>
<p v-if="hasMultipleMnemonics">
You have multiple accounts. If you didn't mean to, talk with Trent.
</p>
</div>
<div>
<p v-if="downloadMnemonic" class="text-green-500">
Here is your seed for account {{ downloadMnemonicAddress?.substring('did:ethr:0x'.length).substring(0, 3) }} -- write it down!
Here is your seed for account
{{
downloadMnemonicAddress
?.substring("did:ethr:0x".length)
.substring(0, 3)
}}
-- write it down!
<br />
{{ downloadMnemonic }}
</p>
@ -533,9 +540,7 @@
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900"
>Keep</span
>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
<span class="text-sm font-bold text-red-900">{{
comparison.differences.accounts.missing.length
@ -549,7 +554,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Add Accounts ({{ comparison.differences.accounts.added.length }}):
Add Accounts ({{
comparison.differences.accounts.added.length
}}):
</h4>
<div class="space-y-1">
<div
@ -559,9 +566,17 @@
>
<div class="font-medium">ID: {{ account.id }}</div>
<div class="text-gray-500">{{ account.did }}</div>
<div class="text-gray-400">Created: {{ account.dateCreated }}</div>
<div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">Has Mnemonic: {{ getAccountHasMnemonic(account) ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Created: {{ account.dateCreated }}
</div>
<div class="text-gray-400">
Has Identity:
{{ getAccountHasIdentity(account) ? "Yes" : "No" }}
</div>
<div class="text-gray-400">
Has Mnemonic:
{{ getAccountHasMnemonic(account) ? "Yes" : "No" }}
</div>
</div>
</div>
</div>
@ -572,7 +587,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Unmodified Accounts ({{ comparison.differences.accounts.unmodified.length }}):
Unmodified Accounts ({{
comparison.differences.accounts.unmodified.length
}}):
</h4>
</div>
<div class="space-y-1">
@ -583,23 +600,39 @@
>
<div class="font-medium">ID: {{ account.id }}</div>
<div class="text-gray-500">{{ account.did }}</div>
<div class="text-gray-400">Created: {{ account.dateCreated }}</div>
<div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">Has Mnemonic: {{ getAccountHasMnemonic(account) ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Created: {{ account.dateCreated }}
</div>
<div class="text-gray-400">
Has Identity:
{{ getAccountHasIdentity(account) ? "Yes" : "No" }}
</div>
<div class="text-gray-400">
Has Mnemonic:
{{ getAccountHasMnemonic(account) ? "Yes" : "No" }}
</div>
</div>
</div>
<!-- Keep Accounts -->
<div
v-if="comparison.differences.accounts.missing.filter(a => a).length > 0"
v-if="
comparison.differences.accounts.missing.filter((a) => a)
.length > 0
"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Keep Accounts ({{ comparison.differences.accounts.missing.filter(a => a).length }}):
Keep Accounts ({{
comparison.differences.accounts.missing.filter((a) => a)
.length
}}):
</h4>
<div class="space-y-1">
<div
v-for="did in comparison.differences.accounts.missing.filter(a => a)"
v-for="did in comparison.differences.accounts.missing.filter(
(a) => a,
)"
:key="did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
@ -675,9 +708,7 @@
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900"
>Keep</span
>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
<span class="text-sm font-bold text-red-900">{{
comparison.differences.settings.missing.length
@ -691,7 +722,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Add Settings ({{ comparison.differences.settings.added.length }}):
Add Settings ({{
comparison.differences.settings.added.length
}}):
</h4>
<div class="space-y-1">
<div
@ -699,9 +732,13 @@
:key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ getSettingDisplayName(setting) }}</div>
<div class="font-medium">
{{ getSettingDisplayName(setting) }}
</div>
<div class="text-gray-500">ID: {{ setting.id }}</div>
<div class="text-gray-400">Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Registered: {{ setting.isRegistered ? "Yes" : "No" }}
</div>
</div>
</div>
</div>
@ -712,7 +749,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Modify Settings ({{ comparison.differences.settings.modified.length }}):
Modify Settings ({{
comparison.differences.settings.modified.length
}}):
</h4>
<div class="space-y-1">
<div
@ -720,9 +759,13 @@
:key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ getSettingDisplayName(setting) }}</div>
<div class="font-medium">
{{ getSettingDisplayName(setting) }}
</div>
<div class="text-gray-500">ID: {{ setting.id }}</div>
<div class="text-gray-400">Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Registered: {{ setting.isRegistered ? "Yes" : "No" }}
</div>
</div>
</div>
</div>
@ -733,7 +776,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Unmodified Settings ({{ comparison.differences.settings.unmodified.length }}):
Unmodified Settings ({{
comparison.differences.settings.unmodified.length
}}):
</h4>
</div>
<div class="space-y-1">
@ -742,29 +787,47 @@
:key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ getSettingDisplayName(setting) }}</div>
<div class="font-medium">
{{ getSettingDisplayName(setting) }}
</div>
<div class="text-gray-500">ID: {{ setting.id }}</div>
<div class="text-gray-400">Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Registered: {{ setting.isRegistered ? "Yes" : "No" }}
</div>
</div>
</div>
<!-- Keep Settings -->
<div
v-if="comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid).length > 0"
v-if="
comparison.differences.settings.missing.filter(
(s) => s.accountDid || s.activeDid,
).length > 0
"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Keep Settings ({{ comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid).length }}):
Keep Settings ({{
comparison.differences.settings.missing.filter(
(s) => s.accountDid || s.activeDid,
).length
}}):
</h4>
<div class="space-y-1">
<div
v-for="setting in comparison.differences.settings.missing.filter(s => s.accountDid || s.activeDid)"
v-for="setting in comparison.differences.settings.missing.filter(
(s) => s.accountDid || s.activeDid,
)"
:key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ getSettingDisplayName(setting) }}</div>
<div class="font-medium">
{{ getSettingDisplayName(setting) }}
</div>
<div class="text-gray-500">ID: {{ setting.id }}</div>
<div class="text-gray-400">Registered: {{ setting.isRegistered ? 'Yes' : 'No' }}</div>
<div class="text-gray-400">
Registered: {{ setting.isRegistered ? "Yes" : "No" }}
</div>
</div>
</div>
</div>
@ -836,9 +899,7 @@
icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2"
/>
<span class="text-sm font-medium text-red-900"
>Keep</span
>
<span class="text-sm font-medium text-red-900">Keep</span>
</div>
<span class="text-sm font-bold text-red-900">{{
comparison.differences.contacts.missing.length
@ -852,7 +913,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Add Contacts ({{ comparison.differences.contacts.added.length }}):
Add Contacts ({{
comparison.differences.contacts.added.length
}}):
</h4>
<div class="space-y-1">
<div
@ -860,9 +923,13 @@
:key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ contact.name || '<empty>' }}</div>
<div class="font-medium">
{{ contact.name || "(empty)" }}
</div>
<div class="text-gray-500">{{ contact.did }}</div>
<div class="text-gray-400">{{ contact.contactMethods?.length || 0 }} contact methods</div>
<div class="text-gray-400">
{{ contact.contactMethods?.length || 0 }} contact methods
</div>
</div>
</div>
</div>
@ -873,7 +940,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Modify Contacts ({{ comparison.differences.contacts.modified.length }}):
Modify Contacts ({{
comparison.differences.contacts.modified.length
}}):
</h4>
<div class="space-y-1">
<div
@ -881,9 +950,13 @@
:key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ contact.name || '<empty>' }}</div>
<div class="font-medium">
{{ contact.name || "(empty)" }}
</div>
<div class="text-gray-500">{{ contact.did }}</div>
<div class="text-gray-400">{{ contact.contactMethods?.length || 0 }} contact methods</div>
<div class="text-gray-400">
{{ contact.contactMethods?.length || 0 }} contact methods
</div>
</div>
</div>
</div>
@ -894,7 +967,9 @@
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Unmodified Contacts ({{ comparison.differences.contacts.unmodified.length }}):
Unmodified Contacts ({{
comparison.differences.contacts.unmodified.length
}}):
</h4>
</div>
<div class="space-y-1">
@ -903,29 +978,45 @@
:key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ contact.name || '<empty>' }}</div>
<div class="font-medium">
{{ contact.name || "(empty)" }}
</div>
<div class="text-gray-500">{{ contact.did }}</div>
<div class="text-gray-400">{{ contact.contactMethods?.length || 0 }} contact methods</div>
<div class="text-gray-400">
{{ contact.contactMethods?.length || 0 }} contact methods
</div>
</div>
</div>
<!-- Keep Contacts -->
<div
v-if="comparison.differences.contacts.missing.filter(c => c.did).length > 0"
v-if="
comparison.differences.contacts.missing.filter((c) => c.did)
.length > 0
"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Keep Contacts ({{ comparison.differences.contacts.missing.filter(c => c.did).length }}):
Keep Contacts ({{
comparison.differences.contacts.missing.filter((c) => c.did)
.length
}}):
</h4>
<div class="space-y-1">
<div
v-for="contact in comparison.differences.contacts.missing.filter(c => c.did)"
v-for="contact in comparison.differences.contacts.missing.filter(
(c) => c.did,
)"
:key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<div class="font-medium">{{ contact.name || '<empty>' }}</div>
<div class="font-medium">
{{ contact.name || "(empty)" }}
</div>
<div class="text-gray-500">{{ contact.did }}</div>
<div class="text-gray-400">{{ contact.contactMethods?.length || 0 }} contact methods</div>
<div class="text-gray-400">
{{ contact.contactMethods?.length || 0 }} contact methods
</div>
</div>
</div>
</div>
@ -939,8 +1030,8 @@
<div v-if="exportedData" class="mt-4 space-y-6">
<h2>Exported Data</h2>
<span
v-on:click="copyToClipboard"
class="text-blue-500 cursor-pointer hover:text-blue-700"
@click="copyToClipboard"
>
Copy to Clipboard
</span>
@ -1025,11 +1116,11 @@ export default class DatabaseMigration extends Vue {
if (setting.type && setting.did) {
return `${setting.type} (${setting.did})`;
}
// Handle live comparison data (has 'activeDid' or 'accountDid' fields)
const did = setting.activeDid || setting.accountDid;
const type = setting.id === 1 ? 'master' : 'account';
return `${type} (${did || 'no DID'})`;
const type = setting.id === 1 ? "master" : "account";
return `${type} (${did || "no DID"})`;
}
/**
@ -1044,7 +1135,7 @@ export default class DatabaseMigration extends Vue {
if (account.hasIdentity !== undefined) {
return account.hasIdentity;
}
// Handle live comparison data (has 'identity' field)
return !!account.identity;
}
@ -1061,7 +1152,7 @@ export default class DatabaseMigration extends Vue {
if (account.hasMnemonic !== undefined) {
return account.hasMnemonic;
}
// Handle live comparison data (has 'mnemonic' field)
return !!account.mnemonic;
}
@ -1071,17 +1162,19 @@ export default class DatabaseMigration extends Vue {
*/
async copyToClipboard(): Promise<void> {
if (!this.exportedData) return;
try {
await this.useClipboard().copy(JSON.stringify(this.exportedData, null, 2));
await this.useClipboard().copy(
JSON.stringify(this.exportedData, null, 2),
);
// Use global window object properly
if (typeof window !== 'undefined') {
window.alert('Copied to clipboard!');
if (typeof window !== "undefined") {
window.alert("Copied to clipboard!");
}
} catch (error) {
logger.error('Failed to copy to clipboard:', error);
if (typeof window !== 'undefined') {
window.alert('Failed to copy to clipboard');
logger.error("Failed to copy to clipboard:", error);
if (typeof window !== "undefined") {
window.alert("Failed to copy to clipboard");
}
}
}
@ -1093,7 +1186,7 @@ export default class DatabaseMigration extends Vue {
let primaryAccount;
if (settings.length > 0) {
const primaryDid = settings[0].activeDid;
primaryAccount = accounts.find(acc => acc.did === primaryDid);
primaryAccount = accounts.find((acc) => acc.did === primaryDid);
if (!primaryAccount) {
this.cannotfindMainAccount = true;
// abort everything
@ -1121,7 +1214,7 @@ export default class DatabaseMigration extends Vue {
this.exportedData = {
accounts: await getDexieAccounts(),
settings: await getDexieSettings(),
contacts: await getDexieContacts()
contacts: await getDexieContacts(),
};
}
@ -1334,15 +1427,15 @@ export default class DatabaseMigration extends Vue {
const yamlData = generateComparisonYaml(this.comparison);
const blob = new Blob([yamlData], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `database-comparison-${new Date().toISOString().split('T')[0]}.json`;
a.download = `database-comparison-${new Date().toISOString().split("T")[0]}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
this.successMessage = "Comparison data exported successfully";
logger.info("[DatabaseMigration] Comparison data exported successfully");
} catch (error) {

Loading…
Cancel
Save