Browse Source

fix linting

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

2
src/db/tables/contacts.ts

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

191
src/services/indexedDBMigrationService.ts

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

241
src/views/DatabaseMigration.vue

@ -9,8 +9,9 @@
databases databases
</p> </p>
<p class="mt-2 text-gray-600"> <p class="mt-2 text-gray-600">
First, we recommend you hit these first two buttons to get your backups, just in case! First, we recommend you hit these first two buttons to get your
If anything doesn't look right when you're done, Trent will help with... anything and everything. 😁 backups, just in case! If anything doesn't look right when you're
done, Trent will help with... anything and everything. 😁
</p> </p>
</div> </div>
@ -50,28 +51,34 @@
</div> </div>
--> -->
<div <div class="text-lg leading-6 font-medium text-red-900 mt-4">
class="text-lg leading-6 font-medium text-red-900 mt-4"
>
<p <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>
<p <p v-if="cannotfindMainAccount">
v-if="cannotfindMainAccount"
>
We cannot find your main account. Talk with Trent. We cannot find your main account. Talk with Trent.
</p> </p>
<p <p v-if="hasMultipleMnemonics">
v-if="hasMultipleMnemonics"
>
You have multiple accounts. If you didn't mean to, talk with Trent. You have multiple accounts. If you didn't mean to, talk with Trent.
</p> </p>
</div> </div>
<div> <div>
<p v-if="downloadMnemonic" class="text-green-500"> <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 /> <br />
{{ downloadMnemonic }} {{ downloadMnemonic }}
</p> </p>
@ -533,9 +540,7 @@
icon-name="trash" icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2" svg-class="h-5 w-5 text-red-600 mr-2"
/> />
<span class="text-sm font-medium text-red-900" <span class="text-sm font-medium text-red-900">Keep</span>
>Keep</span
>
</div> </div>
<span class="text-sm font-bold text-red-900">{{ <span class="text-sm font-bold text-red-900">{{
comparison.differences.accounts.missing.length comparison.differences.accounts.missing.length
@ -549,7 +554,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <div
@ -559,9 +566,17 @@
> >
<div class="font-medium">ID: {{ account.id }}</div> <div class="font-medium">ID: {{ account.id }}</div>
<div class="text-gray-500">{{ account.did }}</div> <div class="text-gray-500">{{ account.did }}</div>
<div class="text-gray-400">Created: {{ account.dateCreated }}</div> <div class="text-gray-400">
<div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div> Created: {{ account.dateCreated }}
<div class="text-gray-400">Has Mnemonic: {{ getAccountHasMnemonic(account) ? 'Yes' : 'No' }}</div> </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> </div>
</div> </div>
@ -572,7 +587,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
@ -583,23 +600,39 @@
> >
<div class="font-medium">ID: {{ account.id }}</div> <div class="font-medium">ID: {{ account.id }}</div>
<div class="text-gray-500">{{ account.did }}</div> <div class="text-gray-500">{{ account.did }}</div>
<div class="text-gray-400">Created: {{ account.dateCreated }}</div> <div class="text-gray-400">
<div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div> Created: {{ account.dateCreated }}
<div class="text-gray-400">Has Mnemonic: {{ getAccountHasMnemonic(account) ? 'Yes' : 'No' }}</div> </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> </div>
<!-- Keep Accounts --> <!-- Keep Accounts -->
<div <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" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <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" :key="did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
> >
@ -675,9 +708,7 @@
icon-name="trash" icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2" svg-class="h-5 w-5 text-red-600 mr-2"
/> />
<span class="text-sm font-medium text-red-900" <span class="text-sm font-medium text-red-900">Keep</span>
>Keep</span
>
</div> </div>
<span class="text-sm font-bold text-red-900">{{ <span class="text-sm font-bold text-red-900">{{
comparison.differences.settings.missing.length comparison.differences.settings.missing.length
@ -691,7 +722,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <div
@ -699,9 +732,13 @@
:key="setting.id" :key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -712,7 +749,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <div
@ -720,9 +759,13 @@
:key="setting.id" :key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -733,7 +776,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
@ -742,29 +787,47 @@
:key="setting.id" :key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
<!-- Keep Settings --> <!-- Keep Settings -->
<div <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" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <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" :key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -836,9 +899,7 @@
icon-name="trash" icon-name="trash"
svg-class="h-5 w-5 text-red-600 mr-2" svg-class="h-5 w-5 text-red-600 mr-2"
/> />
<span class="text-sm font-medium text-red-900" <span class="text-sm font-medium text-red-900">Keep</span>
>Keep</span
>
</div> </div>
<span class="text-sm font-bold text-red-900">{{ <span class="text-sm font-bold text-red-900">{{
comparison.differences.contacts.missing.length comparison.differences.contacts.missing.length
@ -852,7 +913,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <div
@ -860,9 +923,13 @@
:key="contact.did" :key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -873,7 +940,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <div
@ -881,9 +950,13 @@
:key="contact.did" :key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -894,7 +967,9 @@
class="mt-4" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
@ -903,29 +978,45 @@
:key="contact.did" :key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
<!-- Keep Contacts --> <!-- Keep Contacts -->
<div <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" class="mt-4"
> >
<h4 class="text-sm font-medium text-gray-900 mb-2"> <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> </h4>
<div class="space-y-1"> <div class="space-y-1">
<div <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" :key="contact.did"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" 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-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> </div>
</div> </div>
@ -939,8 +1030,8 @@
<div v-if="exportedData" class="mt-4 space-y-6"> <div v-if="exportedData" class="mt-4 space-y-6">
<h2>Exported Data</h2> <h2>Exported Data</h2>
<span <span
v-on:click="copyToClipboard"
class="text-blue-500 cursor-pointer hover:text-blue-700" class="text-blue-500 cursor-pointer hover:text-blue-700"
@click="copyToClipboard"
> >
Copy to Clipboard Copy to Clipboard
</span> </span>
@ -1028,8 +1119,8 @@ export default class DatabaseMigration extends Vue {
// Handle live comparison data (has 'activeDid' or 'accountDid' fields) // Handle live comparison data (has 'activeDid' or 'accountDid' fields)
const did = setting.activeDid || setting.accountDid; const did = setting.activeDid || setting.accountDid;
const type = setting.id === 1 ? 'master' : 'account'; const type = setting.id === 1 ? "master" : "account";
return `${type} (${did || 'no DID'})`; return `${type} (${did || "no DID"})`;
} }
/** /**
@ -1073,15 +1164,17 @@ export default class DatabaseMigration extends Vue {
if (!this.exportedData) return; if (!this.exportedData) return;
try { 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 // Use global window object properly
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
window.alert('Copied to clipboard!'); window.alert("Copied to clipboard!");
} }
} catch (error) { } catch (error) {
logger.error('Failed to copy to clipboard:', error); logger.error("Failed to copy to clipboard:", error);
if (typeof window !== 'undefined') { if (typeof window !== "undefined") {
window.alert('Failed to copy to clipboard'); window.alert("Failed to copy to clipboard");
} }
} }
} }
@ -1093,7 +1186,7 @@ export default class DatabaseMigration extends Vue {
let primaryAccount; let primaryAccount;
if (settings.length > 0) { if (settings.length > 0) {
const primaryDid = settings[0].activeDid; const primaryDid = settings[0].activeDid;
primaryAccount = accounts.find(acc => acc.did === primaryDid); primaryAccount = accounts.find((acc) => acc.did === primaryDid);
if (!primaryAccount) { if (!primaryAccount) {
this.cannotfindMainAccount = true; this.cannotfindMainAccount = true;
// abort everything // abort everything
@ -1121,7 +1214,7 @@ export default class DatabaseMigration extends Vue {
this.exportedData = { this.exportedData = {
accounts: await getDexieAccounts(), accounts: await getDexieAccounts(),
settings: await getDexieSettings(), settings: await getDexieSettings(),
contacts: await getDexieContacts() contacts: await getDexieContacts(),
}; };
} }
@ -1337,7 +1430,7 @@ export default class DatabaseMigration extends Vue {
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; 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); document.body.appendChild(a);
a.click(); a.click();
document.body.removeChild(a); document.body.removeChild(a);

Loading…
Cancel
Save