|
@ -26,12 +26,11 @@ 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, SettingsWithJsonStrings, BoundingBox } from "../db/tables/settings"; |
|
|
import { Settings, MASTER_SETTINGS_KEY, BoundingBox } from "../db/tables/settings"; |
|
|
import { Account, AccountEncrypted } from "../db/tables/accounts"; |
|
|
import { Account } from "../db/tables/accounts"; |
|
|
import { logger } from "../utils/logger"; |
|
|
import { logger } from "../utils/logger"; |
|
|
import { mapColumnsToValues, parseJsonField } from "../db/databaseUtil"; |
|
|
import { mapColumnsToValues, parseJsonField, generateUpdateStatement, generateInsertStatement } 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 |
|
@ -1028,228 +1027,47 @@ export async function migrateSettings( |
|
|
try { |
|
|
try { |
|
|
const dexieSettings = await getDexieSettings(); |
|
|
const dexieSettings = await getDexieSettings(); |
|
|
const platformService = PlatformServiceFactory.getInstance(); |
|
|
const platformService = PlatformServiceFactory.getInstance(); |
|
|
|
|
|
// loop through dexieSettings,
|
|
|
// Group settings by DID to handle duplicates
|
|
|
// load the one with the matching accountDid from sqlite,
|
|
|
const settingsByDid = new Map<string, { |
|
|
// and if one doesn't exist then insert it,
|
|
|
master?: Settings; |
|
|
// otherwise, update the fields
|
|
|
account?: Settings; |
|
|
dexieSettings.forEach(async (setting) => { |
|
|
}>(); |
|
|
const sqliteSettingRaw = await platformService.dbQuery( |
|
|
|
|
|
"SELECT * FROM settings WHERE accountDid = ?", |
|
|
// Organize settings by DID
|
|
|
[setting.accountDid] |
|
|
dexieSettings.forEach(setting => { |
|
|
|
|
|
const isMasterSetting = setting.id === MASTER_SETTINGS_KEY; |
|
|
|
|
|
const did = isMasterSetting ? setting.activeDid : setting.accountDid; |
|
|
|
|
|
|
|
|
|
|
|
if (!did) { |
|
|
|
|
|
result.warnings.push(`Setting ${setting.id} has no DID, skipping`); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (!settingsByDid.has(did)) { |
|
|
|
|
|
settingsByDid.set(did, {}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const didSettings = settingsByDid.get(did)!; |
|
|
|
|
|
if (isMasterSetting) { |
|
|
|
|
|
didSettings.master = setting; |
|
|
|
|
|
logger.info("[MigrationService] Found master settings", { |
|
|
|
|
|
did, |
|
|
|
|
|
id: setting.id, |
|
|
|
|
|
firstName: setting.firstName, |
|
|
|
|
|
isRegistered: setting.isRegistered, |
|
|
|
|
|
profileImageUrl: setting.profileImageUrl, |
|
|
|
|
|
showShortcutBvc: setting.showShortcutBvc, |
|
|
|
|
|
searchBoxes: setting.searchBoxes |
|
|
|
|
|
}); |
|
|
|
|
|
} else { |
|
|
|
|
|
didSettings.account = setting; |
|
|
|
|
|
logger.info("[MigrationService] Found account settings", { |
|
|
|
|
|
did, |
|
|
|
|
|
id: setting.id, |
|
|
|
|
|
firstName: setting.firstName, |
|
|
|
|
|
isRegistered: setting.isRegistered, |
|
|
|
|
|
profileImageUrl: setting.profileImageUrl, |
|
|
|
|
|
showShortcutBvc: setting.showShortcutBvc, |
|
|
|
|
|
searchBoxes: setting.searchBoxes |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Process each unique DID's settings
|
|
|
|
|
|
for (const [did, didSettings] of settingsByDid.entries()) { |
|
|
|
|
|
try { |
|
|
|
|
|
// Process master settings
|
|
|
|
|
|
if (didSettings.master) { |
|
|
|
|
|
const masterData = { |
|
|
|
|
|
id: MASTER_SETTINGS_KEY, |
|
|
|
|
|
activeDid: did, |
|
|
|
|
|
accountDid: "", // Empty for master settings
|
|
|
|
|
|
apiServer: didSettings.master.apiServer || "", |
|
|
|
|
|
filterFeedByNearby: didSettings.master.filterFeedByNearby || false, |
|
|
|
|
|
filterFeedByVisible: didSettings.master.filterFeedByVisible || false, |
|
|
|
|
|
finishedOnboarding: didSettings.master.finishedOnboarding || false, |
|
|
|
|
|
firstName: didSettings.master.firstName || "", |
|
|
|
|
|
hideRegisterPromptOnNewContact: didSettings.master.hideRegisterPromptOnNewContact || false, |
|
|
|
|
|
isRegistered: didSettings.master.isRegistered || false, |
|
|
|
|
|
lastName: didSettings.master.lastName || "", |
|
|
|
|
|
profileImageUrl: didSettings.master.profileImageUrl || "", |
|
|
|
|
|
searchBoxes: didSettings.master.searchBoxes || [], |
|
|
|
|
|
showShortcutBvc: didSettings.master.showShortcutBvc || false |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Check if master setting exists
|
|
|
|
|
|
const existingMaster = await platformService.dbQuery( |
|
|
|
|
|
"SELECT id FROM settings WHERE id = ? AND activeDid = ? AND accountDid = ''", |
|
|
|
|
|
[MASTER_SETTINGS_KEY, did] |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (existingMaster?.values?.length) { |
|
|
|
|
|
logger.info("[MigrationService] Updating master settings", { did, masterData }); |
|
|
|
|
|
await platformService.dbQuery( |
|
|
|
|
|
`UPDATE settings SET
|
|
|
|
|
|
activeDid = ?, |
|
|
|
|
|
accountDid = ?, |
|
|
|
|
|
firstName = ?, |
|
|
|
|
|
isRegistered = ?, |
|
|
|
|
|
profileImageUrl = ?, |
|
|
|
|
|
showShortcutBvc = ?, |
|
|
|
|
|
searchBoxes = ? |
|
|
|
|
|
WHERE id = ?`,
|
|
|
|
|
|
[ |
|
|
|
|
|
masterData.activeDid, |
|
|
|
|
|
masterData.accountDid, |
|
|
|
|
|
masterData.firstName, |
|
|
|
|
|
masterData.isRegistered, |
|
|
|
|
|
masterData.profileImageUrl, |
|
|
|
|
|
masterData.showShortcutBvc, |
|
|
|
|
|
JSON.stringify(masterData.searchBoxes), |
|
|
|
|
|
MASTER_SETTINGS_KEY |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
if (sqliteSettingRaw?.values?.length) { |
|
|
|
|
|
// should cover the master settings, were accountDid is null
|
|
|
|
|
|
const sqliteSetting = mapColumnsToValues(sqliteSettingRaw.columns, sqliteSettingRaw.values) as unknown as Settings; |
|
|
|
|
|
let conditional: string; |
|
|
|
|
|
let preparams: unknown[]; |
|
|
|
|
|
if (!setting.accountDid) { |
|
|
|
|
|
conditional = "accountDid is null"; |
|
|
|
|
|
preparams = []; |
|
|
} else { |
|
|
} else { |
|
|
logger.info("[MigrationService] Inserting master settings", { did, masterData }); |
|
|
conditional = "accountDid = ?"; |
|
|
await platformService.dbQuery( |
|
|
preparams = [setting.accountDid]; |
|
|
`INSERT INTO settings (
|
|
|
|
|
|
id, |
|
|
|
|
|
activeDid, |
|
|
|
|
|
accountDid, |
|
|
|
|
|
firstName, |
|
|
|
|
|
isRegistered, |
|
|
|
|
|
profileImageUrl, |
|
|
|
|
|
showShortcutBvc, |
|
|
|
|
|
searchBoxes |
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[ |
|
|
|
|
|
MASTER_SETTINGS_KEY, |
|
|
|
|
|
masterData.activeDid, |
|
|
|
|
|
masterData.accountDid, |
|
|
|
|
|
masterData.firstName, |
|
|
|
|
|
masterData.isRegistered, |
|
|
|
|
|
masterData.profileImageUrl, |
|
|
|
|
|
masterData.showShortcutBvc, |
|
|
|
|
|
JSON.stringify(masterData.searchBoxes) |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
|
|
|
result.settingsMigrated++; |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
const { sql, params } = generateUpdateStatement( |
|
|
// Process account settings
|
|
|
sqliteSetting as unknown as Record<string, unknown>, |
|
|
if (didSettings.account) { |
|
|
"settings", |
|
|
const accountData = { |
|
|
conditional, |
|
|
id: 2, // Account settings always use id 2
|
|
|
preparams |
|
|
activeDid: "", // Empty for account settings
|
|
|
|
|
|
accountDid: did, |
|
|
|
|
|
apiServer: didSettings.account.apiServer || "", |
|
|
|
|
|
filterFeedByNearby: didSettings.account.filterFeedByNearby || false, |
|
|
|
|
|
filterFeedByVisible: didSettings.account.filterFeedByVisible || false, |
|
|
|
|
|
finishedOnboarding: didSettings.account.finishedOnboarding || false, |
|
|
|
|
|
firstName: didSettings.account.firstName || "", |
|
|
|
|
|
hideRegisterPromptOnNewContact: didSettings.account.hideRegisterPromptOnNewContact || false, |
|
|
|
|
|
isRegistered: didSettings.account.isRegistered || false, |
|
|
|
|
|
lastName: didSettings.account.lastName || "", |
|
|
|
|
|
profileImageUrl: didSettings.account.profileImageUrl || "", |
|
|
|
|
|
searchBoxes: didSettings.account.searchBoxes || [], |
|
|
|
|
|
showShortcutBvc: didSettings.account.showShortcutBvc || false |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Check if account setting exists
|
|
|
|
|
|
const existingAccount = await platformService.dbQuery( |
|
|
|
|
|
"SELECT id FROM settings WHERE id = ? AND accountDid = ? AND activeDid = ''", |
|
|
|
|
|
[2, did] |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
if (existingAccount?.values?.length) { |
|
|
|
|
|
logger.info("[MigrationService] Updating account settings", { did, accountData }); |
|
|
|
|
|
await platformService.dbQuery( |
|
|
|
|
|
`UPDATE settings SET
|
|
|
|
|
|
activeDid = ?, |
|
|
|
|
|
accountDid = ?, |
|
|
|
|
|
firstName = ?, |
|
|
|
|
|
isRegistered = ?, |
|
|
|
|
|
profileImageUrl = ?, |
|
|
|
|
|
showShortcutBvc = ?, |
|
|
|
|
|
searchBoxes = ? |
|
|
|
|
|
WHERE id = ?`,
|
|
|
|
|
|
[ |
|
|
|
|
|
accountData.activeDid, |
|
|
|
|
|
accountData.accountDid, |
|
|
|
|
|
accountData.firstName, |
|
|
|
|
|
accountData.isRegistered, |
|
|
|
|
|
accountData.profileImageUrl, |
|
|
|
|
|
accountData.showShortcutBvc, |
|
|
|
|
|
JSON.stringify(accountData.searchBoxes), |
|
|
|
|
|
2 |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
); |
|
|
|
|
|
await platformService.dbExec(sql, params); |
|
|
|
|
|
result.settingsMigrated++; |
|
|
} else { |
|
|
} else { |
|
|
logger.info("[MigrationService] Inserting account settings", { did, accountData }); |
|
|
// insert new setting
|
|
|
await platformService.dbQuery( |
|
|
delete setting.activeDid; // ensure we don't set the activeDid (since master settings are an update and don't hit this case)
|
|
|
`INSERT INTO settings (
|
|
|
const { sql, params } = generateInsertStatement( |
|
|
id, |
|
|
setting as unknown as Record<string, unknown>, |
|
|
activeDid, |
|
|
"settings" |
|
|
accountDid, |
|
|
|
|
|
firstName, |
|
|
|
|
|
isRegistered, |
|
|
|
|
|
profileImageUrl, |
|
|
|
|
|
showShortcutBvc, |
|
|
|
|
|
searchBoxes |
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[ |
|
|
|
|
|
2, |
|
|
|
|
|
accountData.activeDid, |
|
|
|
|
|
accountData.accountDid, |
|
|
|
|
|
accountData.firstName, |
|
|
|
|
|
accountData.isRegistered, |
|
|
|
|
|
accountData.profileImageUrl, |
|
|
|
|
|
accountData.showShortcutBvc, |
|
|
|
|
|
JSON.stringify(accountData.searchBoxes) |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
); |
|
|
} |
|
|
await platformService.dbExec(sql, params); |
|
|
result.settingsMigrated++; |
|
|
result.settingsMigrated++; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
logger.info("[MigrationService] Successfully migrated settings for DID", { |
|
|
|
|
|
did, |
|
|
|
|
|
masterMigrated: !!didSettings.master, |
|
|
|
|
|
accountMigrated: !!didSettings.account |
|
|
|
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
} catch (error) { |
|
|
|
|
|
const errorMessage = `Failed to migrate settings for DID ${did}: ${error}`; |
|
|
|
|
|
result.errors.push(errorMessage); |
|
|
|
|
|
logger.error("[MigrationService] Settings migration failed:", { |
|
|
|
|
|
error, |
|
|
|
|
|
did |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (result.errors.length > 0) { |
|
|
|
|
|
result.success = false; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return result; |
|
|
return result; |
|
|
} catch (error) { |
|
|
} catch (error) { |
|
|
const errorMessage = `Settings migration failed: ${error}`; |
|
|
const errorMessage = `Settings migration failed: ${error}`; |
|
@ -1277,7 +1095,6 @@ export async function migrateSettings( |
|
|
* |
|
|
* |
|
|
* @async |
|
|
* @async |
|
|
* @function migrateAccounts |
|
|
* @function migrateAccounts |
|
|
* @param {boolean} [overwriteExisting=false] - Whether to overwrite existing accounts in SQLite |
|
|
|
|
|
* @returns {Promise<MigrationResult>} Detailed results of the migration operation |
|
|
* @returns {Promise<MigrationResult>} Detailed results of the migration operation |
|
|
* @throws {Error} If the migration process fails completely |
|
|
* @throws {Error} If the migration process fails completely |
|
|
* @example |
|
|
* @example |
|
@ -1294,12 +1111,8 @@ export async function migrateSettings( |
|
|
* } |
|
|
* } |
|
|
* ``` |
|
|
* ``` |
|
|
*/ |
|
|
*/ |
|
|
export async function migrateAccounts( |
|
|
export async function migrateAccounts(): Promise<MigrationResult> { |
|
|
overwriteExisting: boolean = false, |
|
|
logger.info("[MigrationService] Starting account migration"); |
|
|
): Promise<MigrationResult> { |
|
|
|
|
|
logger.info("[MigrationService] Starting account migration", { |
|
|
|
|
|
overwriteExisting, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const result: MigrationResult = { |
|
|
const result: MigrationResult = { |
|
|
success: true, |
|
|
success: true, |
|
@ -1335,67 +1148,17 @@ export async function migrateAccounts( |
|
|
[did] |
|
|
[did] |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
if (existingResult?.values?.length && !overwriteExisting) { |
|
|
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) { |
|
|
// Map Dexie fields to SQLite fields
|
|
|
await importFromMnemonic(account.mnemonic, account.derivationPath); |
|
|
const accountData = { |
|
|
result.accountsMigrated++; |
|
|
did: account.did, |
|
|
|
|
|
dateCreated: account.dateCreated, |
|
|
|
|
|
derivationPath: account.derivationPath || "", |
|
|
|
|
|
identityEncrBase64: account.identity || "", |
|
|
|
|
|
mnemonicEncrBase64: account.mnemonic || "", |
|
|
|
|
|
passkeyCredIdHex: account.passkeyCredIdHex || "", |
|
|
|
|
|
publicKeyHex: account.publicKeyHex || "" |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Insert or update the account
|
|
|
|
|
|
if (existingResult?.values?.length) { |
|
|
|
|
|
await platformService.dbQuery( |
|
|
|
|
|
`UPDATE accounts SET
|
|
|
|
|
|
dateCreated = ?, |
|
|
|
|
|
derivationPath = ?, |
|
|
|
|
|
identityEncrBase64 = ?, |
|
|
|
|
|
mnemonicEncrBase64 = ?, |
|
|
|
|
|
passkeyCredIdHex = ?, |
|
|
|
|
|
publicKeyHex = ? |
|
|
|
|
|
WHERE did = ?`,
|
|
|
|
|
|
[ |
|
|
|
|
|
accountData.dateCreated, |
|
|
|
|
|
accountData.derivationPath, |
|
|
|
|
|
accountData.identityEncrBase64, |
|
|
|
|
|
accountData.mnemonicEncrBase64, |
|
|
|
|
|
accountData.passkeyCredIdHex, |
|
|
|
|
|
accountData.publicKeyHex, |
|
|
|
|
|
did |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
|
|
|
} else { |
|
|
} else { |
|
|
await platformService.dbQuery( |
|
|
result.errors.push(`Account with DID ${did} has no mnemonic, skipping`); |
|
|
`INSERT INTO accounts (
|
|
|
|
|
|
did, |
|
|
|
|
|
dateCreated, |
|
|
|
|
|
derivationPath, |
|
|
|
|
|
identityEncrBase64, |
|
|
|
|
|
mnemonicEncrBase64, |
|
|
|
|
|
passkeyCredIdHex, |
|
|
|
|
|
publicKeyHex |
|
|
|
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
|
|
|
|
[ |
|
|
|
|
|
did, |
|
|
|
|
|
accountData.dateCreated, |
|
|
|
|
|
accountData.derivationPath, |
|
|
|
|
|
accountData.identityEncrBase64, |
|
|
|
|
|
accountData.mnemonicEncrBase64, |
|
|
|
|
|
accountData.passkeyCredIdHex, |
|
|
|
|
|
accountData.publicKeyHex |
|
|
|
|
|
] |
|
|
|
|
|
); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
result.accountsMigrated++; |
|
|
|
|
|
logger.info("[MigrationService] Successfully migrated account", { |
|
|
logger.info("[MigrationService] Successfully migrated account", { |
|
|
did, |
|
|
did, |
|
|
dateCreated: account.dateCreated |
|
|
dateCreated: account.dateCreated |
|
@ -1424,99 +1187,6 @@ export async function migrateAccounts( |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Generates SQL INSERT statement and parameters from a model object |
|
|
|
|
|
* |
|
|
|
|
|
* This helper function creates a parameterized SQL INSERT statement |
|
|
|
|
|
* from a JavaScript object. It filters out undefined values and |
|
|
|
|
|
* creates the appropriate SQL syntax with placeholders. |
|
|
|
|
|
* |
|
|
|
|
|
* The function is used internally by the migration functions to |
|
|
|
|
|
* safely insert data into the SQLite database. |
|
|
|
|
|
* |
|
|
|
|
|
* @function generateInsertStatement |
|
|
|
|
|
* @param {Record<string, unknown>} model - The model object containing fields to insert |
|
|
|
|
|
* @param {string} tableName - The name of the table to insert into |
|
|
|
|
|
* @returns {Object} Object containing the SQL statement and parameters array |
|
|
|
|
|
* @returns {string} returns.sql - The SQL INSERT statement |
|
|
|
|
|
* @returns {unknown[]} returns.params - Array of parameter values |
|
|
|
|
|
* @example |
|
|
|
|
|
* ```typescript
|
|
|
|
|
|
* const contact = { did: 'did:example:123', name: 'John Doe' }; |
|
|
|
|
|
* const { sql, params } = generateInsertStatement(contact, 'contacts'); |
|
|
|
|
|
* // sql: "INSERT INTO contacts (did, name) VALUES (?, ?)"
|
|
|
|
|
|
* // params: ['did:example:123', 'John Doe']
|
|
|
|
|
|
* ``` |
|
|
|
|
|
*/ |
|
|
|
|
|
function generateInsertStatement( |
|
|
|
|
|
model: Record<string, unknown>, |
|
|
|
|
|
tableName: string, |
|
|
|
|
|
): { sql: string; params: unknown[] } { |
|
|
|
|
|
const columns = Object.keys(model).filter((key) => model[key] !== undefined); |
|
|
|
|
|
const values = Object.values(model).filter((value) => value !== undefined); |
|
|
|
|
|
const placeholders = values.map(() => "?").join(", "); |
|
|
|
|
|
const insertSql = `INSERT INTO ${tableName} (${columns.join(", ")}) VALUES (${placeholders})`; |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
sql: insertSql, |
|
|
|
|
|
params: values, |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* Generates SQL UPDATE statement and parameters from a model object |
|
|
|
|
|
* |
|
|
|
|
|
* This helper function creates a parameterized SQL UPDATE statement |
|
|
|
|
|
* from a JavaScript object. It filters out undefined values and |
|
|
|
|
|
* creates the appropriate SQL syntax with placeholders. |
|
|
|
|
|
* |
|
|
|
|
|
* The function is used internally by the migration functions to |
|
|
|
|
|
* safely update data in the SQLite database. |
|
|
|
|
|
* |
|
|
|
|
|
* @function generateUpdateStatement |
|
|
|
|
|
* @param {Record<string, unknown>} model - The model object containing fields to update |
|
|
|
|
|
* @param {string} tableName - The name of the table to update |
|
|
|
|
|
* @param {string} whereClause - The WHERE clause for the update (e.g. "id = ?") |
|
|
|
|
|
* @param {unknown[]} [whereParams=[]] - Parameters for the WHERE clause |
|
|
|
|
|
* @returns {Object} Object containing the SQL statement and parameters array |
|
|
|
|
|
* @returns {string} returns.sql - The SQL UPDATE statement |
|
|
|
|
|
* @returns {unknown[]} returns.params - Array of parameter values |
|
|
|
|
|
* @example |
|
|
|
|
|
* ```typescript
|
|
|
|
|
|
* const contact = { name: 'Jane Doe' }; |
|
|
|
|
|
* const { sql, params } = generateUpdateStatement(contact, 'contacts', 'did = ?', ['did:example:123']); |
|
|
|
|
|
* // sql: "UPDATE contacts SET name = ? WHERE did = ?"
|
|
|
|
|
|
* // params: ['Jane Doe', 'did:example:123']
|
|
|
|
|
|
* ``` |
|
|
|
|
|
*/ |
|
|
|
|
|
function generateUpdateStatement( |
|
|
|
|
|
model: Record<string, unknown>, |
|
|
|
|
|
tableName: string, |
|
|
|
|
|
whereClause: string, |
|
|
|
|
|
whereParams: unknown[] = [], |
|
|
|
|
|
): { sql: string; params: unknown[] } { |
|
|
|
|
|
const setClauses: string[] = []; |
|
|
|
|
|
const params: unknown[] = []; |
|
|
|
|
|
|
|
|
|
|
|
Object.entries(model).forEach(([key, value]) => { |
|
|
|
|
|
if (value !== undefined) { |
|
|
|
|
|
setClauses.push(`${key} = ?`); |
|
|
|
|
|
params.push(value); |
|
|
|
|
|
} |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
if (setClauses.length === 0) { |
|
|
|
|
|
throw new Error("No valid fields to update"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const sql = `UPDATE ${tableName} SET ${setClauses.join(", ")} WHERE ${whereClause}`; |
|
|
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
|
sql, |
|
|
|
|
|
params: [...params, ...whereParams], |
|
|
|
|
|
}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
/** |
|
|
* Migrates all data from Dexie to SQLite in the proper order |
|
|
* Migrates all data from Dexie to SQLite in the proper order |
|
|
* |
|
|
* |
|
@ -1551,7 +1221,7 @@ export async function migrateAll( |
|
|
|
|
|
|
|
|
// Step 1: Migrate Accounts (foundational)
|
|
|
// Step 1: Migrate Accounts (foundational)
|
|
|
logger.info("[MigrationService] Step 1: Migrating accounts..."); |
|
|
logger.info("[MigrationService] Step 1: Migrating accounts..."); |
|
|
const accountsResult = await migrateAccounts(overwriteExisting); |
|
|
const accountsResult = await migrateAccounts(); |
|
|
if (!accountsResult.success) { |
|
|
if (!accountsResult.success) { |
|
|
result.errors.push( |
|
|
result.errors.push( |
|
|
`Account migration failed: ${accountsResult.errors.join(", ")}`, |
|
|
`Account migration failed: ${accountsResult.errors.join(", ")}`, |
|
|