diff --git a/doc/activeDid-migration-plan.md b/doc/activeDid-migration-plan.md index 0b954e99..2615374b 100644 --- a/doc/activeDid-migration-plan.md +++ b/doc/activeDid-migration-plan.md @@ -65,6 +65,14 @@ Follow this implementation checklist step-by-step to complete the migration. -- Insert default record (will be updated during migration) INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now')); + + -- MIGRATE EXISTING DATA: Copy activeDid from settings to active_identity + -- This prevents data loss when migration runs on existing databases + UPDATE active_identity + SET activeDid = (SELECT activeDid FROM settings WHERE id = 1), + lastUpdated = datetime('now') + WHERE id = 1 + AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''); `, }, ``` @@ -282,11 +290,9 @@ async function migrateActiveDidToSeparateTable(): Promise { result.warnings.push(`Successfully migrated activeDid: ${activeDid}`); } catch (error) { - result.errors.push(`Migration failed: ${error}`); - logger.error("[ActiveDid Migration] Critical error during migration:", error); + logger.error("[PlatformServiceMixin] Error getting active identity:", error); + throw error; } - - return result; } ``` diff --git a/src/db-sql/migration.ts b/src/db-sql/migration.ts index 67944b75..4bf0921c 100644 --- a/src/db-sql/migration.ts +++ b/src/db-sql/migration.ts @@ -124,6 +124,33 @@ const MIGRATIONS = [ ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE; `, }, + { + name: "003_active_did_separate_table", + sql: ` + -- Create new active_identity table with proper constraints + CREATE TABLE IF NOT EXISTS active_identity ( + id INTEGER PRIMARY KEY CHECK (id = 1), + activeDid TEXT NOT NULL, + lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), + FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE CASCADE + ); + + -- Add performance indexes + CREATE INDEX IF NOT EXISTS idx_active_identity_activeDid ON active_identity(activeDid); + CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id); + + -- Insert default record (will be updated during migration) + INSERT OR IGNORE INTO active_identity (id, activeDid, lastUpdated) VALUES (1, '', datetime('now')); + + -- MIGRATE EXISTING DATA: Copy activeDid from settings to active_identity + -- This prevents data loss when migration runs on existing databases + UPDATE active_identity + SET activeDid = (SELECT activeDid FROM settings WHERE id = 1), + lastUpdated = datetime('now') + WHERE id = 1 + AND EXISTS (SELECT 1 FROM settings WHERE id = 1 AND activeDid IS NOT NULL AND activeDid != ''); + `, + }, ]; /** diff --git a/src/db/tables/activeIdentity.ts b/src/db/tables/activeIdentity.ts new file mode 100644 index 00000000..60366bd3 --- /dev/null +++ b/src/db/tables/activeIdentity.ts @@ -0,0 +1,14 @@ +/** + * ActiveIdentity type describes the active identity selection. + * This replaces the activeDid field in the settings table for better + * database architecture and data integrity. + * + * @author Matthew Raymer + * @since 2025-08-29 + */ + +export interface ActiveIdentity { + id: number; + activeDid: string; + lastUpdated: string; +} diff --git a/src/utils/PlatformServiceMixin.ts b/src/utils/PlatformServiceMixin.ts index 010d79ec..9d98c085 100644 --- a/src/utils/PlatformServiceMixin.ts +++ b/src/utils/PlatformServiceMixin.ts @@ -58,6 +58,7 @@ import { generateInsertStatement, generateUpdateStatement, } from "@/utils/sqlHelpers"; +import { ActiveIdentity } from "@/db/tables/activeIdentity"; // ================================================= // TYPESCRIPT INTERFACES @@ -548,6 +549,40 @@ export const PlatformServiceMixin = { } }, + /** + * Get active identity from the new active_identity table + * This replaces the activeDid field in settings for better architecture + */ + async $getActiveIdentity(): Promise { + try { + const result = await this.$dbQuery( + "SELECT id, activeDid, lastUpdated FROM active_identity WHERE id = 1", + ); + + if (result?.values?.length) { + const [id, activeDid, lastUpdated] = result.values[0]; + return { + id: id as number, + activeDid: activeDid as string, + lastUpdated: lastUpdated as string, + }; + } + + // Return default if no record exists + return { + id: 1, + activeDid: "", + lastUpdated: new Date().toISOString(), + }; + } catch (error) { + logger.error( + "[PlatformServiceMixin] Error getting active identity:", + error, + ); + throw error; + } + }, + /** * Transaction wrapper with automatic rollback on error */