Browse Source

IndexedDB migration: fix loading of data, fix object comparisons, add unmodified, etc

migrate-dexie-to-sqlite
Trent Larson 7 days ago
parent
commit
6136cafd11
  1. 4
      src/db/tables/contacts.ts
  2. 5
      src/db/tables/settings.ts
  3. 244
      src/services/indexedDBMigrationService.ts
  4. 131
      src/views/DatabaseMigration.vue
  5. 9
      src/views/TestView.vue

4
src/db/tables/contacts.ts

@ -19,6 +19,10 @@ export interface Contact {
registered?: boolean; // cached value of the server setting registered?: boolean; // cached value of the server setting
} }
export type ContactWithJsonStrings = Contact & {
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
}; };

5
src/db/tables/settings.ts

@ -64,6 +64,11 @@ export type Settings = {
webPushServer?: string; // Web Push server URL webPushServer?: string; // Web Push server URL
}; };
// type of settings where the searchBoxes are JSON strings instead of objects
export type SettingsWithJsonStrings = Settings & {
searchBoxes: string;
};
export function checkIsAnyFeedFilterOn(settings: Settings): boolean { export function checkIsAnyFeedFilterOn(settings: Settings): boolean {
return !!(settings?.filterFeedByNearby || settings?.filterFeedByVisible); return !!(settings?.filterFeedByNearby || settings?.filterFeedByVisible);
} }

244
src/services/indexedDBMigrationService.ts

@ -26,11 +26,12 @@ 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 } from "../db/tables/settings"; import { Settings, MASTER_SETTINGS_KEY, SettingsWithJsonStrings, BoundingBox } from "../db/tables/settings";
import { Account } from "../db/tables/accounts"; import { Account, AccountEncrypted } from "../db/tables/accounts";
import { logger } from "../utils/logger"; import { logger } from "../utils/logger";
import { parseJsonField } from "../db/databaseUtil"; import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
import { importFromMnemonic } from "../libs/util"; import { importFromMnemonic } from "../libs/util";
import { IIdentifier } from "@veramo/core";
/** /**
* Interface for data comparison results between Dexie and SQLite databases * Interface for data comparison results between Dexie and SQLite databases
@ -66,22 +67,24 @@ export interface DataComparison {
dexieSettings: Settings[]; dexieSettings: Settings[];
sqliteSettings: Settings[]; sqliteSettings: Settings[];
dexieAccounts: Account[]; dexieAccounts: Account[];
sqliteAccounts: Account[]; sqliteAccounts: string[];
differences: { differences: {
contacts: { contacts: {
added: Contact[]; added: Contact[];
modified: Contact[]; modified: Contact[];
unmodified: Contact[];
missing: Contact[]; missing: Contact[];
}; };
settings: { settings: {
added: Settings[]; added: Settings[];
modified: Settings[]; modified: Settings[];
unmodified: Settings[];
missing: Settings[]; missing: Settings[];
}; };
accounts: { accounts: {
added: Account[]; added: Account[];
modified: Account[]; unmodified: Account[];
missing: Account[]; missing: string[];
}; };
}; };
} }
@ -184,22 +187,14 @@ export async function getSqliteContacts(): Promise<Contact[]> {
let contacts: Contact[] = []; let contacts: Contact[] = [];
if (result?.values?.length) { if (result?.values?.length) {
contacts = result.values.map((row) => { const preContacts = mapColumnsToValues(result.columns, result.values) as unknown as Contact[];
const contact = parseJsonField(row, {}) as Contact; // This is redundant since absurd-sql auto-parses JSON strings to objects.
return { // But we started it, and it should be known everywhere, so we're keeping it.
did: contact.did || "", contacts = preContacts.map((contact) => {
name: contact.name || "", if (contact.contactMethods) {
contactMethods: parseJsonField( contact.contactMethods = parseJsonField(contact.contactMethods, []) as ContactMethod[];
contact.contactMethods, }
[], return contact;
) as ContactMethod[],
nextPubKeyHashB64: contact.nextPubKeyHashB64 || "",
notes: contact.notes || "",
profileImageUrl: contact.profileImageUrl || "",
publicKeyBase64: contact.publicKeyBase64 || "",
seesMe: contact.seesMe || false,
registered: contact.registered || false,
} as Contact;
}); });
} }
@ -281,37 +276,16 @@ export async function getSqliteSettings(): Promise<Settings[]> {
let settings: Settings[] = []; let settings: Settings[] = [];
if (result?.values?.length) { if (result?.values?.length) {
settings = result.values.map((row) => { const presettings =
const setting = parseJsonField(row, {}) as Settings; mapColumnsToValues(result.columns, result.values) as Settings[];
return { // This is redundant since absurd-sql auto-parses JSON strings to objects.
id: setting.id, // But we started it, and it should be known everywhere, so we're keeping it.
accountDid: setting.accountDid || "", settings = presettings.map((setting) => {
activeDid: setting.activeDid || "", if (setting.searchBoxes) {
apiServer: setting.apiServer || "", setting.searchBoxes = parseJsonField(setting.searchBoxes, []) as Array<{ name: string, bbox: BoundingBox }>;
filterFeedByNearby: setting.filterFeedByNearby || false, }
filterFeedByVisible: setting.filterFeedByVisible || false, return setting;
finishedOnboarding: setting.finishedOnboarding || false, })
firstName: setting.firstName || "",
hideRegisterPromptOnNewContact: setting.hideRegisterPromptOnNewContact || false,
isRegistered: setting.isRegistered || false,
lastName: setting.lastName || "",
lastAckedOfferToUserJwtId: setting.lastAckedOfferToUserJwtId || "",
lastAckedOfferToUserProjectsJwtId: setting.lastAckedOfferToUserProjectsJwtId || "",
lastNotifiedClaimId: setting.lastNotifiedClaimId || "",
lastViewedClaimId: setting.lastViewedClaimId || "",
notifyingNewActivityTime: setting.notifyingNewActivityTime || "",
notifyingReminderMessage: setting.notifyingReminderMessage || "",
notifyingReminderTime: setting.notifyingReminderTime || "",
partnerApiServer: setting.partnerApiServer || "",
passkeyExpirationMinutes: setting.passkeyExpirationMinutes,
profileImageUrl: setting.profileImageUrl || "",
searchBoxes: parseJsonField(setting.searchBoxes, []),
showContactGivesInline: setting.showContactGivesInline || false,
showGeneralAdvanced: setting.showGeneralAdvanced || false,
showShortcutBvc: setting.showShortcutBvc || false,
vapid: setting.vapid || "",
} as Settings;
});
} }
logger.info( logger.info(
@ -336,7 +310,7 @@ export async function getSqliteSettings(): Promise<Settings[]> {
* *
* @async * @async
* @function getSqliteAccounts * @function getSqliteAccounts
* @returns {Promise<Account[]>} Array of all accounts from SQLite database * @returns {Promise<string[]>} Array of all accounts from SQLite database
* @throws {Error} If database query fails or data conversion fails * @throws {Error} If database query fails or data conversion fails
* @example * @example
* ```typescript * ```typescript
@ -348,32 +322,20 @@ export async function getSqliteSettings(): Promise<Settings[]> {
* } * }
* ``` * ```
*/ */
export async function getSqliteAccounts(): Promise<Account[]> { export async function getSqliteAccounts(): Promise<string[]> {
try { try {
const platformService = PlatformServiceFactory.getInstance(); const platformService = PlatformServiceFactory.getInstance();
const result = await platformService.dbQuery("SELECT * FROM accounts"); const result = await platformService.dbQuery("SELECT did FROM accounts");
let accounts: Account[] = []; let dids: string[] = [];
if (result?.values?.length) { if (result?.values?.length) {
accounts = result.values.map((row) => { dids = result.values.map((row) => row[0] as string);
const account = parseJsonField(row, {}) as Account;
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( logger.info(
`[MigrationService] Retrieved ${accounts.length} accounts from SQLite`, `[MigrationService] Retrieved ${dids.length} accounts from SQLite`,
); );
return accounts; return dids;
} catch (error) { } catch (error) {
logger.error("[MigrationService] Error retrieving SQLite accounts:", error); logger.error("[MigrationService] Error retrieving SQLite accounts:", error);
throw new Error(`Failed to retrieve SQLite accounts: ${error}`); throw new Error(`Failed to retrieve SQLite accounts: ${error}`);
@ -532,6 +494,7 @@ export async function compareDatabases(): Promise<DataComparison> {
function compareContacts(dexieContacts: Contact[], sqliteContacts: Contact[]) { function compareContacts(dexieContacts: Contact[], sqliteContacts: Contact[]) {
const added: Contact[] = []; const added: Contact[] = [];
const modified: Contact[] = []; const modified: Contact[] = [];
const unmodified: Contact[] = [];
const missing: Contact[] = []; const missing: Contact[] = [];
// Find contacts that exist in Dexie but not in SQLite // Find contacts that exist in Dexie but not in SQLite
@ -543,6 +506,8 @@ function compareContacts(dexieContacts: Contact[], sqliteContacts: Contact[]) {
added.push(dexieContact); added.push(dexieContact);
} else if (!contactsEqual(dexieContact, sqliteContact)) { } else if (!contactsEqual(dexieContact, sqliteContact)) {
modified.push(dexieContact); modified.push(dexieContact);
} else {
unmodified.push(dexieContact);
} }
} }
@ -554,7 +519,7 @@ function compareContacts(dexieContacts: Contact[], sqliteContacts: Contact[]) {
} }
} }
return { added, modified, missing }; return { added, modified, unmodified, missing };
} }
/** /**
@ -588,27 +553,30 @@ function compareSettings(
) { ) {
const added: Settings[] = []; const added: Settings[] = [];
const modified: Settings[] = []; const modified: Settings[] = [];
const unmodified: Settings[] = [];
const missing: Settings[] = []; const missing: Settings[] = [];
// 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.id === dexieSetting.id); 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)) {
modified.push(dexieSetting); modified.push(dexieSetting);
} else {
unmodified.push(dexieSetting);
} }
} }
// 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.id === sqliteSetting.id); const dexieSetting = dexieSettings.find((s) => s.accountDid === sqliteSetting.accountDid);
if (!dexieSetting) { if (!dexieSetting) {
missing.push(sqliteSetting); missing.push(sqliteSetting);
} }
} }
return { added, modified, missing }; return { added, modified, unmodified, missing };
} }
/** /**
@ -623,11 +591,11 @@ function compareSettings(
* *
* @function compareAccounts * @function compareAccounts
* @param {Account[]} dexieAccounts - Accounts from Dexie database * @param {Account[]} dexieAccounts - Accounts from Dexie database
* @param {Account[]} sqliteAccounts - Accounts from SQLite database * @param {Account[]} sqliteDids - Accounts from SQLite database
* @returns {Object} Object containing added, modified, and missing accounts * @returns {Object} Object containing added, modified, and missing accounts
* @returns {Account[]} returns.added - Accounts in Dexie but not SQLite * @returns {Account[]} returns.added - Accounts in Dexie but not SQLite
* @returns {Account[]} returns.modified - Accounts that differ between databases * @returns {Account[]} returns.modified - always 0 because we don't check
* @returns {Account[]} returns.missing - Accounts in SQLite but not Dexie * @returns {string[]} returns.missing - Accounts in SQLite but not Dexie
* @example * @example
* ```typescript * ```typescript
* const differences = compareAccounts(dexieAccounts, sqliteAccounts); * const differences = compareAccounts(dexieAccounts, sqliteAccounts);
@ -636,30 +604,30 @@ function compareSettings(
* console.log(`Missing: ${differences.missing.length}`); * console.log(`Missing: ${differences.missing.length}`);
* ``` * ```
*/ */
function compareAccounts(dexieAccounts: Account[], sqliteAccounts: Account[]) { function compareAccounts(dexieAccounts: Account[], sqliteDids: string[]) {
const added: Account[] = []; const added: Account[] = [];
const modified: Account[] = []; const unmodified: Account[] = [];
const missing: Account[] = []; const missing: string[] = [];
// Find accounts that exist in Dexie but not in SQLite // Find accounts that exist in Dexie but not in SQLite
for (const dexieAccount of dexieAccounts) { for (const dexieAccount of dexieAccounts) {
const sqliteAccount = sqliteAccounts.find((a) => a.id === dexieAccount.id); const sqliteDid = sqliteDids.find((a) => a === dexieAccount.did);
if (!sqliteAccount) { if (!sqliteDid) {
added.push(dexieAccount); added.push(dexieAccount);
} else if (!accountsEqual(dexieAccount, sqliteAccount)) { } else {
modified.push(dexieAccount); unmodified.push(dexieAccount);
} }
} }
// Find accounts that exist in SQLite but not in Dexie // Find accounts that exist in SQLite but not in Dexie
for (const sqliteAccount of sqliteAccounts) { for (const sqliteDid of sqliteDids) {
const dexieAccount = dexieAccounts.find((a) => a.id === sqliteAccount.id); const dexieAccount = dexieAccounts.find((a) => a.did === sqliteDid);
if (!dexieAccount) { if (!dexieAccount) {
missing.push(sqliteAccount); missing.push(sqliteDid);
} }
} }
return { added, modified, missing }; return { added, unmodified, missing };
} }
/** /**
@ -688,15 +656,15 @@ function compareAccounts(dexieAccounts: Account[], sqliteAccounts: Account[]) {
*/ */
function contactsEqual(contact1: Contact, contact2: Contact): boolean { function contactsEqual(contact1: Contact, contact2: Contact): boolean {
return ( return (
contact1.did === contact2.did && contact1.did == contact2.did &&
contact1.name === contact2.name && contact1.name == contact2.name &&
contact1.notes === contact2.notes && contact1.notes == contact2.notes &&
contact1.profileImageUrl === contact2.profileImageUrl && contact1.profileImageUrl == contact2.profileImageUrl &&
contact1.publicKeyBase64 === contact2.publicKeyBase64 && contact1.publicKeyBase64 == contact2.publicKeyBase64 &&
contact1.nextPubKeyHashB64 === contact2.nextPubKeyHashB64 && contact1.nextPubKeyHashB64 == contact2.nextPubKeyHashB64 &&
contact1.seesMe === contact2.seesMe && contact1.seesMe == contact2.seesMe &&
contact1.registered === contact2.registered && contact1.registered == contact2.registered &&
JSON.stringify(contact1.contactMethods) === JSON.stringify(contact1.contactMethods) ==
JSON.stringify(contact2.contactMethods) JSON.stringify(contact2.contactMethods)
); );
} }
@ -727,38 +695,38 @@ function contactsEqual(contact1: Contact, contact2: Contact): boolean {
*/ */
function settingsEqual(settings1: Settings, settings2: Settings): boolean { function settingsEqual(settings1: Settings, settings2: Settings): boolean {
return ( return (
settings1.id === settings2.id && settings1.id == settings2.id &&
settings1.accountDid === settings2.accountDid && settings1.accountDid == settings2.accountDid &&
settings1.activeDid === settings2.activeDid && settings1.activeDid == settings2.activeDid &&
settings1.apiServer === settings2.apiServer && settings1.apiServer == settings2.apiServer &&
settings1.filterFeedByNearby === settings2.filterFeedByNearby && settings1.filterFeedByNearby == settings2.filterFeedByNearby &&
settings1.filterFeedByVisible === settings2.filterFeedByVisible && settings1.filterFeedByVisible == settings2.filterFeedByVisible &&
settings1.finishedOnboarding === settings2.finishedOnboarding && settings1.finishedOnboarding == settings2.finishedOnboarding &&
settings1.firstName === settings2.firstName && settings1.firstName == settings2.firstName &&
settings1.hideRegisterPromptOnNewContact === settings1.hideRegisterPromptOnNewContact ==
settings2.hideRegisterPromptOnNewContact && settings2.hideRegisterPromptOnNewContact &&
settings1.isRegistered === settings2.isRegistered && settings1.isRegistered == settings2.isRegistered &&
settings1.lastName === settings2.lastName && settings1.lastName == settings2.lastName &&
settings1.lastAckedOfferToUserJwtId === settings1.lastAckedOfferToUserJwtId ==
settings2.lastAckedOfferToUserJwtId && settings2.lastAckedOfferToUserJwtId &&
settings1.lastAckedOfferToUserProjectsJwtId === settings1.lastAckedOfferToUserProjectsJwtId ==
settings2.lastAckedOfferToUserProjectsJwtId && settings2.lastAckedOfferToUserProjectsJwtId &&
settings1.lastNotifiedClaimId === settings2.lastNotifiedClaimId && settings1.lastNotifiedClaimId == settings2.lastNotifiedClaimId &&
settings1.lastViewedClaimId === settings2.lastViewedClaimId && settings1.lastViewedClaimId == settings2.lastViewedClaimId &&
settings1.notifyingNewActivityTime === settings2.notifyingNewActivityTime && settings1.notifyingNewActivityTime == settings2.notifyingNewActivityTime &&
settings1.notifyingReminderMessage === settings2.notifyingReminderMessage && settings1.notifyingReminderMessage == settings2.notifyingReminderMessage &&
settings1.notifyingReminderTime === settings2.notifyingReminderTime && settings1.notifyingReminderTime == settings2.notifyingReminderTime &&
settings1.partnerApiServer === settings2.partnerApiServer && settings1.partnerApiServer == settings2.partnerApiServer &&
settings1.passkeyExpirationMinutes === settings2.passkeyExpirationMinutes && settings1.passkeyExpirationMinutes == settings2.passkeyExpirationMinutes &&
settings1.profileImageUrl === settings2.profileImageUrl && settings1.profileImageUrl == settings2.profileImageUrl &&
settings1.showContactGivesInline === settings2.showContactGivesInline && settings1.showContactGivesInline == settings2.showContactGivesInline &&
settings1.showGeneralAdvanced === settings2.showGeneralAdvanced && settings1.showGeneralAdvanced == settings2.showGeneralAdvanced &&
settings1.showShortcutBvc === settings2.showShortcutBvc && settings1.showShortcutBvc == settings2.showShortcutBvc &&
settings1.vapid === settings2.vapid && settings1.vapid == settings2.vapid &&
settings1.warnIfProdServer === settings2.warnIfProdServer && settings1.warnIfProdServer == settings2.warnIfProdServer &&
settings1.warnIfTestServer === settings2.warnIfTestServer && settings1.warnIfTestServer == settings2.warnIfTestServer &&
settings1.webPushServer === settings2.webPushServer && settings1.webPushServer == settings2.webPushServer &&
JSON.stringify(settings1.searchBoxes) === JSON.stringify(settings1.searchBoxes) ==
JSON.stringify(settings2.searchBoxes) JSON.stringify(settings2.searchBoxes)
); );
} }
@ -830,23 +798,25 @@ export function generateComparisonYaml(comparison: DataComparison): string {
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.did).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,
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,
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,
modified: comparison.differences.accounts.modified.length, unmodified: comparison.differences.accounts.unmodified.length,
missing: comparison.differences.accounts.missing.filter(a => a.did).length, missing: comparison.differences.accounts.missing.filter(a => a).length,
}, },
}, },
details: { details: {
@ -888,15 +858,9 @@ export function generateComparisonYaml(comparison: DataComparison): string {
hasIdentity: !!a.identity, hasIdentity: !!a.identity,
hasMnemonic: !!a.mnemonic, hasMnemonic: !!a.mnemonic,
})), })),
sqlite: comparison.sqliteAccounts sqlite: comparison.sqliteAccounts.map((a) => ({
.filter(a => a.did) did: a,
.map((a) => ({ })),
id: a.id,
did: a.did,
dateCreated: a.dateCreated,
hasIdentity: !!a.identity,
hasMnemonic: !!a.mnemonic,
})),
}, },
}, },
}; };

131
src/views/DatabaseMigration.vue

@ -74,7 +74,7 @@
</div> </div>
<div> <div>
<p v-if="downloadMnemonic" class="text-green-500"> <p v-if="downloadMnemonic" class="text-green-500">
Here is your seed. 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>
@ -506,15 +506,15 @@
> >
<div class="flex items-center"> <div class="flex items-center">
<IconRenderer <IconRenderer
icon-name="edit" icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2" svg-class="h-5 w-5 text-yellow-600 mr-2"
/> />
<span class="text-sm font-medium text-yellow-900" <span class="text-sm font-medium text-yellow-900"
>Modify</span >Unmodified</span
> >
</div> </div>
<span class="text-sm font-bold text-yellow-900">{{ <span class="text-sm font-bold text-yellow-900">{{
comparison.differences.accounts.modified.length comparison.differences.accounts.unmodified.length
}}</span> }}</span>
</div> </div>
@ -559,48 +559,44 @@
</div> </div>
</div> </div>
<!-- Modify Accounts --> <!-- Unmodified Accounts -->
<div <div
v-if="comparison.differences.accounts.modified.length > 0" v-if="comparison.differences.accounts.unmodified.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">
Modify Accounts ({{ comparison.differences.accounts.modified.length }}): Unmodified Accounts ({{ comparison.differences.accounts.unmodified.length }}):
</h4> </h4>
<div class="space-y-1"> </div>
<div <div class="space-y-1">
v-for="account in comparison.differences.accounts.modified" <div
:key="account.id" v-for="account in comparison.differences.accounts.unmodified"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded" :key="account.id"
> class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
<div class="font-medium">ID: {{ account.id }}</div> >
<div class="text-gray-500">{{ account.did }}</div> <div class="font-medium">ID: {{ account.id }}</div>
<div class="text-gray-400">Created: {{ account.dateCreated }}</div> <div class="text-gray-500">{{ account.did }}</div>
<div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div> <div class="text-gray-400">Created: {{ account.dateCreated }}</div>
<div class="text-gray-400">Has Mnemonic: {{ getAccountHasMnemonic(account) ? 'Yes' : 'No' }}</div> <div class="text-gray-400">Has Identity: {{ getAccountHasIdentity(account) ? 'Yes' : 'No' }}</div>
</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.did).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.did).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="account in comparison.differences.accounts.missing.filter(a => a.did)" v-for="did in comparison.differences.accounts.missing.filter(a => a)"
:key="account.id" :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"
> >
<div class="font-medium">ID: {{ account.id }}</div> <div class="text-gray-500">{{ did }}</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> </div>
</div> </div>
</div> </div>
@ -647,6 +643,23 @@
}}</span> }}</span>
</div> </div>
<div
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Unmodified</span
>
</div>
<span class="text-sm font-bold text-yellow-900">{{
comparison.differences.settings.unmodified.length
}}</span>
</div>
<div <div
class="flex items-center justify-between p-3 bg-red-50 rounded-lg" class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
> >
@ -707,6 +720,27 @@
</div> </div>
</div> </div>
<!-- Unmodified Settings -->
<div
v-if="comparison.differences.settings.unmodified.length > 0"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Unmodified Settings ({{ comparison.differences.settings.unmodified.length }}):
</h4>
</div>
<div class="space-y-1">
<div
v-for="setting in comparison.differences.settings.unmodified"
:key="setting.id"
class="text-xs text-gray-600 bg-gray-50 p-2 rounded"
>
<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>
</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"
@ -770,6 +804,23 @@
}}</span> }}</span>
</div> </div>
<div
class="flex items-center justify-between p-3 bg-yellow-50 rounded-lg"
>
<div class="flex items-center">
<IconRenderer
icon-name="check"
svg-class="h-5 w-5 text-yellow-600 mr-2"
/>
<span class="text-sm font-medium text-yellow-900"
>Unmodified</span
>
</div>
<span class="text-sm font-bold text-yellow-900">{{
comparison.differences.contacts.unmodified.length
}}</span>
</div>
<div <div
class="flex items-center justify-between p-3 bg-red-50 rounded-lg" class="flex items-center justify-between p-3 bg-red-50 rounded-lg"
> >
@ -830,6 +881,27 @@
</div> </div>
</div> </div>
<!-- Unmodified Contacts -->
<div
v-if="comparison.differences.contacts.unmodified.length > 0"
class="mt-4"
>
<h4 class="text-sm font-medium text-gray-900 mb-2">
Unmodified Contacts ({{ comparison.differences.contacts.unmodified.length }}):
</h4>
</div>
<div class="space-y-1">
<div
v-for="contact in comparison.differences.contacts.unmodified"
: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="text-gray-500">{{ contact.did }}</div>
<div class="text-gray-400">{{ contact.contactMethods?.length || 0 }} contact methods</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"
@ -871,7 +943,6 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { ref } from "vue";
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { useClipboard } from "@vueuse/core"; import { useClipboard } from "@vueuse/core";
@ -923,6 +994,7 @@ export default class DatabaseMigration extends Vue {
private cannotfindMainAccount = false; private cannotfindMainAccount = false;
private downloadSettingsContactsBlob?: Blob; private downloadSettingsContactsBlob?: Blob;
private downloadMnemonic?: string; private downloadMnemonic?: string;
private downloadMnemonicAddress?: string;
private hasMultipleMnemonics = false; private hasMultipleMnemonics = false;
private isLoading = false; private isLoading = false;
private loadingMessage = ""; private loadingMessage = "";
@ -1024,6 +1096,7 @@ export default class DatabaseMigration extends Vue {
} }
if (primaryAccount) { if (primaryAccount) {
this.downloadMnemonic = primaryAccount.mnemonic; this.downloadMnemonic = primaryAccount.mnemonic;
this.downloadMnemonicAddress = primaryAccount.did;
} }
} }

9
src/views/TestView.vue

@ -182,6 +182,15 @@
> >
Accounts Accounts
</button> </button>
<button
class="text-sm text-blue-600 hover:text-blue-800 underline"
@click="
sqlQuery = 'SELECT * FROM contacts;';
executeSql();
"
>
Contacts
</button>
<button <button
class="text-sm text-blue-600 hover:text-blue-800 underline" class="text-sm text-blue-600 hover:text-blue-800 underline"
@click=" @click="

Loading…
Cancel
Save