Browse Source

fix: Resolve database migration issues and consolidate 003 migrations

- Fix $getActiveIdentity() logic flow preventing false "empty table" warnings
- Implement auto-selection of first account when activeDid is null after migration
- Consolidate 003 and 003b migrations into single 003 migration
- Re-introduce foreign key constraint for activeDid referential integrity
- Add comprehensive debug logging for migration troubleshooting
- Remove 003b validation logic and update migration name mapping

Fixes migration from master to active_did_redux branch and ensures system
always has valid activeDid for proper functionality.
pull/188/head
Matthew Raymer 5 days ago
parent
commit
616bef655a
  1. 16
      src/db-sql/migration.ts
  2. 67
      src/services/migrationService.ts
  3. 42
      src/utils/PlatformServiceMixin.ts

16
src/db-sql/migration.ts

@ -145,17 +145,25 @@ const MIGRATIONS = [
id INTEGER PRIMARY KEY CHECK (id = 1), id INTEGER PRIMARY KEY CHECK (id = 1),
activeDid TEXT DEFAULT NULL, -- NULL instead of empty string activeDid TEXT DEFAULT NULL, -- NULL instead of empty string
lastUpdated TEXT NOT NULL DEFAULT (datetime('now')), lastUpdated TEXT NOT NULL DEFAULT (datetime('now')),
FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE RESTRICT FOREIGN KEY (activeDid) REFERENCES accounts(did) ON DELETE SET NULL
); );
-- Add performance indexes -- Add performance indexes
CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id); CREATE UNIQUE INDEX IF NOT EXISTS idx_active_identity_single_record ON active_identity(id);
-- Seed singleton row -- Seed singleton row (only if not already exists)
INSERT INTO active_identity (id, activeDid, lastUpdated) VALUES (1, NULL, datetime('now')); -- Use a more explicit approach to ensure the row gets inserted
INSERT INTO active_identity (id, activeDid, lastUpdated)
SELECT 1, NULL, datetime('now')
WHERE NOT EXISTS (SELECT 1 FROM active_identity WHERE id = 1);
-- Add hasBackedUpSeed field to settings (from registration-prompt-parity) -- Add hasBackedUpSeed field to settings (consolidated from 003b)
-- This may fail if column already exists from master branch migration
-- The error handling will catch this and mark migration as applied
ALTER TABLE settings ADD COLUMN hasBackedUpSeed BOOLEAN DEFAULT FALSE; ALTER TABLE settings ADD COLUMN hasBackedUpSeed BOOLEAN DEFAULT FALSE;
-- Debug: Verify the row was inserted
SELECT 'DEBUG: Row count after insertion' as debug_message, COUNT(*) as row_count FROM active_identity;
`, `,
}, },
]; ];

67
src/services/migrationService.ts

@ -280,7 +280,7 @@ async function validateMigrationApplication<T>(
error, error,
); );
} }
} else if (migration.name === "003_active_did_separation") { } else if (migration.name === "003_active_identity_and_seed_backup") {
// Validate active_identity table exists and has correct structure // Validate active_identity table exists and has correct structure
try { try {
// Check that active_identity table exists // Check that active_identity table exists
@ -322,6 +322,22 @@ async function validateMigrationApplication<T>(
error, error,
); );
} }
} else if (migration.name === "003b_add_hasBackedUpSeed_to_settings") {
// Validate hasBackedUpSeed column exists in settings table
try {
await sqlQuery(`SELECT hasBackedUpSeed FROM settings LIMIT 1`);
validation.isValid = true;
validation.hasExpectedColumns = true;
} catch (error) {
validation.isValid = false;
validation.errors.push(
`Column hasBackedUpSeed missing from settings table`,
);
logger.error(
`❌ [Migration-Validation] Column hasBackedUpSeed missing:`,
error,
);
}
} }
// Add validation for future migrations here // Add validation for future migrations here
@ -385,7 +401,7 @@ async function isSchemaAlreadyPresent<T>(
// Reduced logging - only log on error // Reduced logging - only log on error
return false; return false;
} }
} else if (migration.name === "003_active_did_separation") { } else if (migration.name === "003_active_identity_and_seed_backup") {
// Check if active_identity table exists and has correct structure // Check if active_identity table exists and has correct structure
try { try {
// Check that active_identity table exists // Check that active_identity table exists
@ -496,14 +512,32 @@ export async function runMigrations<T>(
); );
`); `);
// Step 2: Get list of already applied migrations // Step 2: Handle migration name changes (master branch compatibility)
// Map old migration names to new ones
const migrationNameMap = new Map([
// No longer needed - migrations consolidated into single 003
]);
// Update any old migration names to new ones
for (const [oldName, newName] of migrationNameMap) {
try {
await sqlExec("UPDATE migrations SET name = ? WHERE name = ?", [newName, oldName]);
if (await sqlQuery("SELECT 1 FROM migrations WHERE name = ? LIMIT 1", [newName])) {
migrationLog(`🔄 [Migration] Renamed migration: ${oldName}${newName}`);
}
} catch (error) {
// Ignore errors - migration might not exist
}
}
// Step 3: Get list of already applied migrations
const appliedMigrationsResult = await sqlQuery( const appliedMigrationsResult = await sqlQuery(
"SELECT name FROM migrations", "SELECT name FROM migrations",
); );
const appliedMigrations = extractMigrationNames(appliedMigrationsResult); const appliedMigrations = extractMigrationNames(appliedMigrationsResult);
// Step 3: Get all registered migrations // Step 4: Get all registered migrations
const migrations = migrationRegistry.getMigrations(); const migrations = migrationRegistry.getMigrations();
if (migrations.length === 0) { if (migrations.length === 0) {
@ -518,7 +552,7 @@ export async function runMigrations<T>(
let appliedCount = 0; let appliedCount = 0;
let skippedCount = 0; let skippedCount = 0;
// Step 4: Process each migration // Step 5: Process each migration
for (const migration of migrations) { for (const migration of migrations) {
// Check 1: Is it recorded as applied in migrations table? // Check 1: Is it recorded as applied in migrations table?
const isRecordedAsApplied = appliedMigrations.has(migration.name); const isRecordedAsApplied = appliedMigrations.has(migration.name);
@ -557,7 +591,26 @@ export async function runMigrations<T>(
try { try {
// Execute the migration SQL // Execute the migration SQL
await sqlExec(migration.sql); migrationLog(`🔧 [Migration] Executing SQL for: ${migration.name}`);
migrationLog(`🔧 [Migration] SQL content: ${migration.sql}`);
const execResult = await sqlExec(migration.sql);
migrationLog(`🔧 [Migration] SQL execution result: ${JSON.stringify(execResult)}`);
// Debug: Check if active_identity table exists and has data
if (migration.name === "003_active_identity_and_seed_backup") {
try {
const tableCheck = await sqlQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='active_identity'");
migrationLog(`🔍 [Migration] Table check result: ${JSON.stringify(tableCheck)}`);
const rowCount = await sqlQuery("SELECT COUNT(*) as count FROM active_identity");
migrationLog(`🔍 [Migration] Row count in active_identity: ${JSON.stringify(rowCount)}`);
const allRows = await sqlQuery("SELECT * FROM active_identity");
migrationLog(`🔍 [Migration] All rows in active_identity: ${JSON.stringify(allRows)}`);
} catch (error) {
migrationLog(`❌ [Migration] Debug query failed: ${JSON.stringify(error)}`);
}
}
// Validate the migration was applied correctly // Validate the migration was applied correctly
const validation = await validateMigrationApplication( const validation = await validateMigrationApplication(
@ -606,6 +659,8 @@ export async function runMigrations<T>(
`⚠️ [Migration] Schema validation failed for ${migration.name}:`, `⚠️ [Migration] Schema validation failed for ${migration.name}:`,
validation.errors, validation.errors,
); );
// Don't mark as applied if validation fails
continue;
} }
// Mark the migration as applied since the schema change already exists // Mark the migration as applied since the schema change already exists

42
src/utils/PlatformServiceMixin.ts

@ -668,6 +668,42 @@ export const PlatformServiceMixin = {
{ activeDid }, { activeDid },
); );
// Handle null activeDid (initial state after migration)
if (activeDid === null) {
logger.debug(
"[PlatformServiceMixin] Active identity is null (initial state), attempting auto-selection",
);
// Try to auto-select first available account
const availableAccounts = await this.$dbQuery(
"SELECT did FROM accounts ORDER BY dateCreated, did LIMIT 1",
);
if (availableAccounts?.values?.length) {
const firstAccountDid = availableAccounts.values[0][0] as string;
logger.debug(
"[PlatformServiceMixin] Auto-selecting first account as active:",
{ firstAccountDid },
);
// Update active_identity table with the first account
await this.$dbExec(
"UPDATE active_identity SET activeDid = ?, lastUpdated = datetime('now') WHERE id = 1",
[firstAccountDid],
);
logger.debug(
"[PlatformServiceMixin] Active identity auto-selected successfully",
);
return { activeDid: firstAccountDid };
} else {
logger.warn(
"[PlatformServiceMixin] No accounts available for auto-selection",
);
return { activeDid: "" };
}
}
// Validate activeDid exists in accounts // Validate activeDid exists in accounts
if (activeDid) { if (activeDid) {
const accountExists = await this.$dbQuery( const accountExists = await this.$dbQuery(
@ -690,6 +726,12 @@ export const PlatformServiceMixin = {
); );
return { activeDid: "" }; return { activeDid: "" };
} }
} else {
// activeDid is empty string, return it
logger.debug(
"[PlatformServiceMixin] Active identity is empty string, returning as-is",
);
return { activeDid: "" };
} }
} }

Loading…
Cancel
Save