forked from jsnbuchanan/crowd-funder-for-time-pwa
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.
This commit is contained in:
@@ -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;
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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: "" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user