From 8e605d04d7fe6303e927837a7c2361cd94a0a09e Mon Sep 17 00:00:00 2001
From: Trent Larson <trent@trentlarson.com>
Date: Thu, 19 Jun 2025 18:12:56 -0600
Subject: [PATCH] IndexedDB migration: fix settings update

---
 src/services/indexedDBMigrationService.ts | 93 ++++++++++++++---------
 src/views/DatabaseMigration.vue           | 25 +++---
 2 files changed, 66 insertions(+), 52 deletions(-)

diff --git a/src/services/indexedDBMigrationService.ts b/src/services/indexedDBMigrationService.ts
index 5aaa1241..f6061b8f 100644
--- a/src/services/indexedDBMigrationService.ts
+++ b/src/services/indexedDBMigrationService.ts
@@ -898,6 +898,10 @@ export function generateComparisonYaml(comparison: DataComparison): string {
  * }
  * ```
  */
+/**
+ * 
+ * I recommend using the existing contact import view to migrate contacts.
+ * 
 export async function migrateContacts(
   overwriteExisting: boolean = false,
 ): Promise<MigrationResult> {
@@ -976,6 +980,8 @@ export async function migrateContacts(
     return result;
   }
 }
+ *
+ */
 
 /**
  * Migrates specific settings fields from Dexie to SQLite database
@@ -991,13 +997,12 @@ export async function migrateContacts(
  *
  * @async
  * @function migrateSettings
- * @param {boolean} [overwriteExisting=false] - Whether to overwrite existing settings in SQLite
  * @returns {Promise<MigrationResult>} Detailed results of the migration operation
  * @throws {Error} If the migration process fails completely
  * @example
  * ```typescript
  * try {
- *   const result = await migrateSettings(true); // Overwrite existing
+ *   const result = await migrateSettings();
  *   if (result.success) {
  *     console.log(`Successfully migrated ${result.settingsMigrated} settings`);
  *   } else {
@@ -1008,12 +1013,8 @@ export async function migrateContacts(
  * }
  * ```
  */
-export async function migrateSettings(
-  overwriteExisting: boolean = false,
-): Promise<MigrationResult> {
-  logger.info("[MigrationService] Starting settings migration", {
-    overwriteExisting,
-  });
+export async function migrateSettings(): Promise<MigrationResult> {
+  logger.info("[MigrationService] Starting settings migration");
 
   const result: MigrationResult = {
     success: true,
@@ -1026,19 +1027,34 @@ export async function migrateSettings(
 
   try {
     const dexieSettings = await getDexieSettings();
+    logger.info("[MigrationService] Migrating settings", {
+      dexieSettings: dexieSettings.length,
+    });
     const platformService = PlatformServiceFactory.getInstance();
-    // loop through dexieSettings,
-    // load the one with the matching accountDid from sqlite,
-    // and if one doesn't exist then insert it,
-    // otherwise, update the fields
-    dexieSettings.forEach(async (setting) => {
-      const sqliteSettingRaw = await platformService.dbQuery(
-        "SELECT * FROM settings WHERE accountDid = ?",
-        [setting.accountDid]
-      );
+    
+    // Create an array of promises for all settings migrations
+    const migrationPromises = dexieSettings.map(async (setting) => {
+      logger.info("[MigrationService] Starting to migrate settings", setting);
+      let sqliteSettingRaw: { columns: string[], values: unknown[][] } | undefined;
+      if (!setting.accountDid) {
+        sqliteSettingRaw = await platformService.dbQuery(
+          "SELECT * FROM settings WHERE accountDid is null"
+        );
+      } else {
+        sqliteSettingRaw = await platformService.dbQuery(
+          "SELECT * FROM settings WHERE accountDid = ?",
+          [setting.accountDid]
+        )
+      }
+      logger.info("[MigrationService] Migrating one set of settings:", {
+        setting,
+        sqliteSettingRaw,
+      });
       if (sqliteSettingRaw?.values?.length) {
         // should cover the master settings, were accountDid is null
-        const sqliteSetting = mapColumnsToValues(sqliteSettingRaw.columns, sqliteSettingRaw.values) as unknown as Settings;
+        const sqliteSettings = mapColumnsToValues(sqliteSettingRaw.columns, sqliteSettingRaw.values) as unknown as Settings[];
+        const sqliteSetting = sqliteSettings[0];
+        console.log('sqliteSetting', sqliteSetting)
         let conditional: string;
         let preparams: unknown[];
         if (!setting.accountDid) {
@@ -1054,7 +1070,7 @@ export async function migrateSettings(
           conditional,
           preparams
         );
-        await platformService.dbExec(sql, params);
+        await platformService.dbExec(sql, params)
         result.settingsMigrated++;
       } else {
         // insert new setting
@@ -1068,12 +1084,17 @@ export async function migrateSettings(
       }
     });
 
+    // Wait for all migrations to complete
+    const updatedSettings = await Promise.all(migrationPromises);
+    
+    logger.info("[MigrationService] Finished migrating settings", updatedSettings, result);
+
     return result;
   } catch (error) {
+    logger.error("[MigrationService] Complete settings migration failed:", error);
     const errorMessage = `Settings migration failed: ${error}`;
     result.errors.push(errorMessage);
     result.success = false;
-    logger.error("[MigrationService] Complete settings migration failed:", error);
     return result;
   }
 }
@@ -1083,7 +1104,7 @@ export async function migrateSettings(
  *
  * This function transfers all accounts from the Dexie database to the
  * SQLite database. It handles both new accounts (INSERT) and existing
- * accounts (UPDATE) based on the overwriteExisting parameter.
+ * accounts (UPDATE).
  *
  * For accounts with mnemonic data, the function uses importFromMnemonic
  * to ensure proper key derivation and identity creation during migration.
@@ -1100,7 +1121,7 @@ export async function migrateSettings(
  * @example
  * ```typescript
  * try {
- *   const result = await migrateAccounts(true); // Overwrite existing
+ *   const result = await migrateAccounts();
  *   if (result.success) {
  *     console.log(`Successfully migrated ${result.accountsMigrated} accounts`);
  *   } else {
@@ -1199,12 +1220,9 @@ export async function migrateAccounts(): Promise<MigrationResult> {
  * The migration runs within a transaction to ensure atomicity. If any step fails,
  * the entire migration is rolled back.
  *
- * @param overwriteExisting - Whether to overwrite existing records in SQLite
  * @returns Promise<MigrationResult> - Detailed result of the migration operation
  */
-export async function migrateAll(
-  overwriteExisting: boolean = false,
-): Promise<MigrationResult> {
+export async function migrateAll(): Promise<MigrationResult> {
   const result: MigrationResult = {
     success: false,
     contactsMigrated: 0,
@@ -1233,7 +1251,7 @@ export async function migrateAll(
 
     // Step 2: Migrate Settings (depends on accounts)
     logger.info("[MigrationService] Step 2: Migrating settings...");
-    const settingsResult = await migrateSettings(overwriteExisting);
+    const settingsResult = await migrateSettings();
     if (!settingsResult.success) {
       result.errors.push(
         `Settings migration failed: ${settingsResult.errors.join(", ")}`,
@@ -1244,16 +1262,17 @@ export async function migrateAll(
     result.warnings.push(...settingsResult.warnings);
 
     // Step 3: Migrate Contacts (independent, but after accounts for consistency)
-    logger.info("[MigrationService] Step 3: Migrating contacts...");
-    const contactsResult = await migrateContacts(overwriteExisting);
-    if (!contactsResult.success) {
-      result.errors.push(
-        `Contact migration failed: ${contactsResult.errors.join(", ")}`,
-      );
-      return result;
-    }
-    result.contactsMigrated = contactsResult.contactsMigrated;
-    result.warnings.push(...contactsResult.warnings);
+    // ... but which is better done through the contact import view
+    // logger.info("[MigrationService] Step 3: Migrating contacts...");
+    // const contactsResult = await migrateContacts();
+    // if (!contactsResult.success) {
+    //   result.errors.push(
+    //     `Contact migration failed: ${contactsResult.errors.join(", ")}`,
+    //   );
+    //   return result;
+    // }
+    // result.contactsMigrated = contactsResult.contactsMigrated;
+    // result.warnings.push(...contactsResult.warnings);
 
     // All migrations successful
     result.success = true;
diff --git a/src/views/DatabaseMigration.vue b/src/views/DatabaseMigration.vue
index 95917e12..bfd3caed 100644
--- a/src/views/DatabaseMigration.vue
+++ b/src/views/DatabaseMigration.vue
@@ -19,8 +19,9 @@
         </a>
       </div>
 
+      <!-- Migration Options -->
+      <!--
       <div class="mt-4">
-        <!-- Migration Options -->
         <div class="bg-white shadow rounded-lg">
           <div class="px-4 py-5 sm:p-6">
             <h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">
@@ -52,6 +53,7 @@
           </div>
         </div>
       </div>
+      -->
 
       <div 
         class="text-lg leading-6 font-medium text-red-900 mt-4"
@@ -59,7 +61,7 @@
         <p
           v-if="comparison && (comparison.sqliteAccounts.length > 1 || comparison.sqliteSettings.length > 1 || comparison.sqliteContacts.length > 0)"
         >
-          Beware: you have unexpected existing data in the SQLite database that will be overwritten. Talk with Trent.
+          Beware: you have unexpected existing data in the new database that will be overwritten. Talk with Trent.
         </p>
         <p
           v-if="cannotfindMainAccount"
@@ -951,7 +953,6 @@ import { Router } from "vue-router";
 import IconRenderer from "../components/IconRenderer.vue";
 import {
   compareDatabases,
-  migrateContacts,
   migrateSettings,
   migrateAccounts,
   migrateAll,
@@ -1004,7 +1005,6 @@ export default class DatabaseMigration extends Vue {
   private loadingMessage = "";
   private error = "";
   private exportedData: Record<string, any> | null = null;
-  private overwriteExisting = true;
   private successMessage = "";
 
   useClipboard = useClipboard;
@@ -1075,7 +1075,7 @@ export default class DatabaseMigration extends Vue {
         window.alert('Copied to clipboard!');
       }
     } catch (error) {
-      console.error('Failed to copy to clipboard:', error);
+      logger.error('Failed to copy to clipboard:', error);
       if (typeof window !== 'undefined') {
         window.alert('Failed to copy to clipboard');
       }
@@ -1141,7 +1141,7 @@ export default class DatabaseMigration extends Vue {
     this.clearMessages();
 
     try {
-      const result: MigrationResult = await migrateAll(this.overwriteExisting);
+      const result: MigrationResult = await migrateAll();
 
       if (result.success) {
         const totalMigrated =
@@ -1209,8 +1209,7 @@ export default class DatabaseMigration extends Vue {
   /**
    * Migrates contacts from Dexie to SQLite database
    *
-   * This method transfers contacts from the Dexie database to SQLite,
-   * with options to overwrite existing records.
+   * This method transfers contacts from the Dexie database to SQLite.
    *
    * @async
    * @returns {Promise<void>}
@@ -1230,8 +1229,7 @@ export default class DatabaseMigration extends Vue {
   /**
    * Migrates settings from Dexie to SQLite database
    *
-   * This method transfers settings from the Dexie database to SQLite,
-   * with options to overwrite existing records.
+   * This method transfers settings from the Dexie database to SQLite.
    *
    * @async
    * @returns {Promise<void>}
@@ -1241,9 +1239,7 @@ export default class DatabaseMigration extends Vue {
     this.clearMessages();
 
     try {
-      const result: MigrationResult = await migrateSettings(
-        this.overwriteExisting,
-      );
+      const result: MigrationResult = await migrateSettings();
 
       if (result.success) {
         this.successMessage = `Successfully migrated ${result.settingsMigrated} settings.`;
@@ -1276,8 +1272,7 @@ export default class DatabaseMigration extends Vue {
    * Migrates accounts from Dexie to SQLite database
    *
    * This method transfers accounts from the Dexie database to SQLite,
-   * with options to overwrite existing records. For accounts with mnemonic
-   * data, it uses the importFromMnemonic utility for proper key derivation.
+   * For accounts with mnemonic data, it uses the importFromMnemonic utility for proper key derivation.
    *
    * @async
    * @returns {Promise<void>}