From 4a22a35b3ed51f4470779363bfbab5be695d1a7b Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 29 Aug 2025 11:48:22 +0000 Subject: [PATCH] feat(activeDid): implement migration to separate active_identity table - Add migration 003 with data migration logic to prevent data loss - Create dedicated ActiveIdentity interface in separate file for better architecture - Implement $getActiveIdentity method in PlatformServiceMixin - Enhance $updateActiveDid with dual-write pattern for backward compatibility - Maintain separation of concerns between settings and active identity types - Follow project architectural pattern with dedicated type definition files The migration creates active_identity table alongside existing settings, automatically copying existing activeDid data to prevent user data loss. Dual-write pattern ensures backward compatibility during transition. Migration includes: - Schema creation with proper constraints and indexes - Automatic data transfer from settings.activeDid to active_identity.activeDid - Validation to ensure data exists before migration - Atomic operation: schema and data migration happen together --- doc/activeDid-migration-plan.md | 7 +++++-- src/db-sql/migration.ts | 27 ++++++++++++++++++++++++ src/db/tables/activeIdentity.ts | 14 +++++++++++++ src/utils/PlatformServiceMixin.ts | 35 +++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/db/tables/activeIdentity.ts diff --git a/doc/activeDid-migration-plan.md b/doc/activeDid-migration-plan.md index 14916ff6..b006aeaa 100644 --- a/doc/activeDid-migration-plan.md +++ b/doc/activeDid-migration-plan.md @@ -117,10 +117,10 @@ runs on existing databases. ### **2. Type Definitions** -Create ActiveIdentity interface in `src/db/tables/settings.ts`: +Create ActiveIdentity interface in `src/db/tables/activeIdentity.ts`: ```typescript -// Add to src/db/tables/settings.ts +// Create new file: src/db/tables/activeIdentity.ts export interface ActiveIdentity { id: number; activeDid: string; @@ -128,6 +128,9 @@ export interface ActiveIdentity { } ``` +**Note**: This maintains separation of concerns by keeping active identity types +separate from settings types, following the project's architectural pattern. + ### **3. PlatformServiceMixin Methods** Implement required methods in `src/utils/PlatformServiceMixin.ts`: 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 */