forked from jsnbuchanan/crowd-funder-for-time-pwa
IndexedDB migration: fix loading of data, fix object comparisons, add unmodified, etc
This commit is contained in:
@@ -26,11 +26,12 @@ 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 } from "../db/tables/settings";
|
||||
import { Account } from "../db/tables/accounts";
|
||||
import { Settings, MASTER_SETTINGS_KEY, SettingsWithJsonStrings, BoundingBox } from "../db/tables/settings";
|
||||
import { Account, AccountEncrypted } from "../db/tables/accounts";
|
||||
import { logger } from "../utils/logger";
|
||||
import { parseJsonField } from "../db/databaseUtil";
|
||||
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil";
|
||||
import { importFromMnemonic } from "../libs/util";
|
||||
import { IIdentifier } from "@veramo/core";
|
||||
|
||||
/**
|
||||
* Interface for data comparison results between Dexie and SQLite databases
|
||||
@@ -66,22 +67,24 @@ export interface DataComparison {
|
||||
dexieSettings: Settings[];
|
||||
sqliteSettings: Settings[];
|
||||
dexieAccounts: Account[];
|
||||
sqliteAccounts: Account[];
|
||||
sqliteAccounts: string[];
|
||||
differences: {
|
||||
contacts: {
|
||||
added: Contact[];
|
||||
modified: Contact[];
|
||||
unmodified: Contact[];
|
||||
missing: Contact[];
|
||||
};
|
||||
settings: {
|
||||
added: Settings[];
|
||||
modified: Settings[];
|
||||
unmodified: Settings[];
|
||||
missing: Settings[];
|
||||
};
|
||||
accounts: {
|
||||
added: Account[];
|
||||
modified: Account[];
|
||||
missing: Account[];
|
||||
unmodified: Account[];
|
||||
missing: string[];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -184,22 +187,14 @@ export async function getSqliteContacts(): Promise<Contact[]> {
|
||||
|
||||
let contacts: Contact[] = [];
|
||||
if (result?.values?.length) {
|
||||
contacts = result.values.map((row) => {
|
||||
const contact = parseJsonField(row, {}) as Contact;
|
||||
return {
|
||||
did: contact.did || "",
|
||||
name: contact.name || "",
|
||||
contactMethods: parseJsonField(
|
||||
contact.contactMethods,
|
||||
[],
|
||||
) as ContactMethod[],
|
||||
nextPubKeyHashB64: contact.nextPubKeyHashB64 || "",
|
||||
notes: contact.notes || "",
|
||||
profileImageUrl: contact.profileImageUrl || "",
|
||||
publicKeyBase64: contact.publicKeyBase64 || "",
|
||||
seesMe: contact.seesMe || false,
|
||||
registered: contact.registered || false,
|
||||
} 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[];
|
||||
}
|
||||
return contact;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -281,37 +276,16 @@ export async function getSqliteSettings(): Promise<Settings[]> {
|
||||
|
||||
let settings: Settings[] = [];
|
||||
if (result?.values?.length) {
|
||||
settings = result.values.map((row) => {
|
||||
const setting = parseJsonField(row, {}) as Settings;
|
||||
return {
|
||||
id: setting.id,
|
||||
accountDid: setting.accountDid || "",
|
||||
activeDid: setting.activeDid || "",
|
||||
apiServer: setting.apiServer || "",
|
||||
filterFeedByNearby: setting.filterFeedByNearby || false,
|
||||
filterFeedByVisible: setting.filterFeedByVisible || false,
|
||||
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;
|
||||
});
|
||||
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 }>;
|
||||
}
|
||||
return setting;
|
||||
})
|
||||
}
|
||||
|
||||
logger.info(
|
||||
@@ -336,7 +310,7 @@ export async function getSqliteSettings(): Promise<Settings[]> {
|
||||
*
|
||||
* @async
|
||||
* @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
|
||||
* @example
|
||||
* ```typescript
|
||||
@@ -348,32 +322,20 @@ export async function getSqliteSettings(): Promise<Settings[]> {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function getSqliteAccounts(): Promise<Account[]> {
|
||||
export async function getSqliteAccounts(): Promise<string[]> {
|
||||
try {
|
||||
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) {
|
||||
accounts = result.values.map((row) => {
|
||||
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;
|
||||
});
|
||||
dids = result.values.map((row) => row[0] as string);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`[MigrationService] Retrieved ${accounts.length} accounts from SQLite`,
|
||||
`[MigrationService] Retrieved ${dids.length} accounts from SQLite`,
|
||||
);
|
||||
return accounts;
|
||||
return dids;
|
||||
} catch (error) {
|
||||
logger.error("[MigrationService] Error retrieving 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[]) {
|
||||
const added: Contact[] = [];
|
||||
const modified: Contact[] = [];
|
||||
const unmodified: Contact[] = [];
|
||||
const missing: Contact[] = [];
|
||||
|
||||
// Find contacts that exist in Dexie but not in SQLite
|
||||
@@ -543,6 +506,8 @@ function compareContacts(dexieContacts: Contact[], sqliteContacts: Contact[]) {
|
||||
added.push(dexieContact);
|
||||
} else if (!contactsEqual(dexieContact, sqliteContact)) {
|
||||
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 modified: Settings[] = [];
|
||||
const unmodified: Settings[] = [];
|
||||
const missing: Settings[] = [];
|
||||
|
||||
// Find settings that exist in Dexie but not in SQLite
|
||||
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) {
|
||||
added.push(dexieSetting);
|
||||
} else if (!settingsEqual(dexieSetting, sqliteSetting)) {
|
||||
modified.push(dexieSetting);
|
||||
} else {
|
||||
unmodified.push(dexieSetting);
|
||||
}
|
||||
}
|
||||
|
||||
// Find settings that exist in SQLite but not in Dexie
|
||||
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) {
|
||||
missing.push(sqliteSetting);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, modified, missing };
|
||||
return { added, modified, unmodified, missing };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -623,11 +591,11 @@ function compareSettings(
|
||||
*
|
||||
* @function compareAccounts
|
||||
* @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 {Account[]} returns.added - Accounts in Dexie but not SQLite
|
||||
* @returns {Account[]} returns.modified - Accounts that differ between databases
|
||||
* @returns {Account[]} returns.missing - Accounts in SQLite but not Dexie
|
||||
* @returns {Account[]} returns.modified - always 0 because we don't check
|
||||
* @returns {string[]} returns.missing - Accounts in SQLite but not Dexie
|
||||
* @example
|
||||
* ```typescript
|
||||
* const differences = compareAccounts(dexieAccounts, sqliteAccounts);
|
||||
@@ -636,30 +604,30 @@ function compareSettings(
|
||||
* console.log(`Missing: ${differences.missing.length}`);
|
||||
* ```
|
||||
*/
|
||||
function compareAccounts(dexieAccounts: Account[], sqliteAccounts: Account[]) {
|
||||
function compareAccounts(dexieAccounts: Account[], sqliteDids: string[]) {
|
||||
const added: Account[] = [];
|
||||
const modified: Account[] = [];
|
||||
const missing: Account[] = [];
|
||||
const unmodified: Account[] = [];
|
||||
const missing: string[] = [];
|
||||
|
||||
// Find accounts that exist in Dexie but not in SQLite
|
||||
for (const dexieAccount of dexieAccounts) {
|
||||
const sqliteAccount = sqliteAccounts.find((a) => a.id === dexieAccount.id);
|
||||
if (!sqliteAccount) {
|
||||
const sqliteDid = sqliteDids.find((a) => a === dexieAccount.did);
|
||||
if (!sqliteDid) {
|
||||
added.push(dexieAccount);
|
||||
} else if (!accountsEqual(dexieAccount, sqliteAccount)) {
|
||||
modified.push(dexieAccount);
|
||||
} else {
|
||||
unmodified.push(dexieAccount);
|
||||
}
|
||||
}
|
||||
|
||||
// Find accounts that exist in SQLite but not in Dexie
|
||||
for (const sqliteAccount of sqliteAccounts) {
|
||||
const dexieAccount = dexieAccounts.find((a) => a.id === sqliteAccount.id);
|
||||
for (const sqliteDid of sqliteDids) {
|
||||
const dexieAccount = dexieAccounts.find((a) => a.did === sqliteDid);
|
||||
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 {
|
||||
return (
|
||||
contact1.did === contact2.did &&
|
||||
contact1.name === contact2.name &&
|
||||
contact1.notes === contact2.notes &&
|
||||
contact1.profileImageUrl === contact2.profileImageUrl &&
|
||||
contact1.publicKeyBase64 === contact2.publicKeyBase64 &&
|
||||
contact1.nextPubKeyHashB64 === contact2.nextPubKeyHashB64 &&
|
||||
contact1.seesMe === contact2.seesMe &&
|
||||
contact1.registered === contact2.registered &&
|
||||
JSON.stringify(contact1.contactMethods) ===
|
||||
contact1.did == contact2.did &&
|
||||
contact1.name == contact2.name &&
|
||||
contact1.notes == contact2.notes &&
|
||||
contact1.profileImageUrl == contact2.profileImageUrl &&
|
||||
contact1.publicKeyBase64 == contact2.publicKeyBase64 &&
|
||||
contact1.nextPubKeyHashB64 == contact2.nextPubKeyHashB64 &&
|
||||
contact1.seesMe == contact2.seesMe &&
|
||||
contact1.registered == contact2.registered &&
|
||||
JSON.stringify(contact1.contactMethods) ==
|
||||
JSON.stringify(contact2.contactMethods)
|
||||
);
|
||||
}
|
||||
@@ -727,38 +695,38 @@ function contactsEqual(contact1: Contact, contact2: Contact): boolean {
|
||||
*/
|
||||
function settingsEqual(settings1: Settings, settings2: Settings): boolean {
|
||||
return (
|
||||
settings1.id === settings2.id &&
|
||||
settings1.accountDid === settings2.accountDid &&
|
||||
settings1.activeDid === settings2.activeDid &&
|
||||
settings1.apiServer === settings2.apiServer &&
|
||||
settings1.filterFeedByNearby === settings2.filterFeedByNearby &&
|
||||
settings1.filterFeedByVisible === settings2.filterFeedByVisible &&
|
||||
settings1.finishedOnboarding === settings2.finishedOnboarding &&
|
||||
settings1.firstName === settings2.firstName &&
|
||||
settings1.hideRegisterPromptOnNewContact ===
|
||||
settings1.id == settings2.id &&
|
||||
settings1.accountDid == settings2.accountDid &&
|
||||
settings1.activeDid == settings2.activeDid &&
|
||||
settings1.apiServer == settings2.apiServer &&
|
||||
settings1.filterFeedByNearby == settings2.filterFeedByNearby &&
|
||||
settings1.filterFeedByVisible == settings2.filterFeedByVisible &&
|
||||
settings1.finishedOnboarding == settings2.finishedOnboarding &&
|
||||
settings1.firstName == settings2.firstName &&
|
||||
settings1.hideRegisterPromptOnNewContact ==
|
||||
settings2.hideRegisterPromptOnNewContact &&
|
||||
settings1.isRegistered === settings2.isRegistered &&
|
||||
settings1.lastName === settings2.lastName &&
|
||||
settings1.lastAckedOfferToUserJwtId ===
|
||||
settings1.isRegistered == settings2.isRegistered &&
|
||||
settings1.lastName == settings2.lastName &&
|
||||
settings1.lastAckedOfferToUserJwtId ==
|
||||
settings2.lastAckedOfferToUserJwtId &&
|
||||
settings1.lastAckedOfferToUserProjectsJwtId ===
|
||||
settings1.lastAckedOfferToUserProjectsJwtId ==
|
||||
settings2.lastAckedOfferToUserProjectsJwtId &&
|
||||
settings1.lastNotifiedClaimId === settings2.lastNotifiedClaimId &&
|
||||
settings1.lastViewedClaimId === settings2.lastViewedClaimId &&
|
||||
settings1.notifyingNewActivityTime === settings2.notifyingNewActivityTime &&
|
||||
settings1.notifyingReminderMessage === settings2.notifyingReminderMessage &&
|
||||
settings1.notifyingReminderTime === settings2.notifyingReminderTime &&
|
||||
settings1.partnerApiServer === settings2.partnerApiServer &&
|
||||
settings1.passkeyExpirationMinutes === settings2.passkeyExpirationMinutes &&
|
||||
settings1.profileImageUrl === settings2.profileImageUrl &&
|
||||
settings1.showContactGivesInline === settings2.showContactGivesInline &&
|
||||
settings1.showGeneralAdvanced === settings2.showGeneralAdvanced &&
|
||||
settings1.showShortcutBvc === settings2.showShortcutBvc &&
|
||||
settings1.vapid === settings2.vapid &&
|
||||
settings1.warnIfProdServer === settings2.warnIfProdServer &&
|
||||
settings1.warnIfTestServer === settings2.warnIfTestServer &&
|
||||
settings1.webPushServer === settings2.webPushServer &&
|
||||
JSON.stringify(settings1.searchBoxes) ===
|
||||
settings1.lastNotifiedClaimId == settings2.lastNotifiedClaimId &&
|
||||
settings1.lastViewedClaimId == settings2.lastViewedClaimId &&
|
||||
settings1.notifyingNewActivityTime == settings2.notifyingNewActivityTime &&
|
||||
settings1.notifyingReminderMessage == settings2.notifyingReminderMessage &&
|
||||
settings1.notifyingReminderTime == settings2.notifyingReminderTime &&
|
||||
settings1.partnerApiServer == settings2.partnerApiServer &&
|
||||
settings1.passkeyExpirationMinutes == settings2.passkeyExpirationMinutes &&
|
||||
settings1.profileImageUrl == settings2.profileImageUrl &&
|
||||
settings1.showContactGivesInline == settings2.showContactGivesInline &&
|
||||
settings1.showGeneralAdvanced == settings2.showGeneralAdvanced &&
|
||||
settings1.showShortcutBvc == settings2.showShortcutBvc &&
|
||||
settings1.vapid == settings2.vapid &&
|
||||
settings1.warnIfProdServer == settings2.warnIfProdServer &&
|
||||
settings1.warnIfTestServer == settings2.warnIfTestServer &&
|
||||
settings1.webPushServer == settings2.webPushServer &&
|
||||
JSON.stringify(settings1.searchBoxes) ==
|
||||
JSON.stringify(settings2.searchBoxes)
|
||||
);
|
||||
}
|
||||
@@ -830,23 +798,25 @@ export function generateComparisonYaml(comparison: DataComparison): string {
|
||||
dexieSettings: comparison.dexieSettings.length,
|
||||
sqliteSettings: comparison.sqliteSettings.filter(s => s.accountDid || s.activeDid).length,
|
||||
dexieAccounts: comparison.dexieAccounts.length,
|
||||
sqliteAccounts: comparison.sqliteAccounts.filter(a => a.did).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,
|
||||
},
|
||||
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,
|
||||
},
|
||||
accounts: {
|
||||
added: comparison.differences.accounts.added.length,
|
||||
modified: comparison.differences.accounts.modified.length,
|
||||
missing: comparison.differences.accounts.missing.filter(a => a.did).length,
|
||||
unmodified: comparison.differences.accounts.unmodified.length,
|
||||
missing: comparison.differences.accounts.missing.filter(a => a).length,
|
||||
},
|
||||
},
|
||||
details: {
|
||||
@@ -888,15 +858,9 @@ export function generateComparisonYaml(comparison: DataComparison): string {
|
||||
hasIdentity: !!a.identity,
|
||||
hasMnemonic: !!a.mnemonic,
|
||||
})),
|
||||
sqlite: comparison.sqliteAccounts
|
||||
.filter(a => a.did)
|
||||
.map((a) => ({
|
||||
id: a.id,
|
||||
did: a.did,
|
||||
dateCreated: a.dateCreated,
|
||||
hasIdentity: !!a.identity,
|
||||
hasMnemonic: !!a.mnemonic,
|
||||
})),
|
||||
sqlite: comparison.sqliteAccounts.map((a) => ({
|
||||
did: a,
|
||||
})),
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user