forked from jsnbuchanan/crowd-funder-for-time-pwa
fix: Resolve TypeScript linting warnings in CapacitorPlatformService
- Replace 'any' type assertions with specific types in migration name extraction
* Change '(row as any).name' to '(row as { name: string }).name'
* Add proper null checks and 'in' operator for property access
- Fix database integrity check type safety
* Change '(col: any)' to '(col: unknown)' with type guards
* Use specific type assertion for column name checking
Resolves: @typescript-eslint/no-explicit-any warnings (2 instances)
Impact: Improves type safety without changing functionality
This commit is contained in:
@@ -175,10 +175,12 @@ export let memoryLogs: string[] = [];
|
||||
* @param level - The log level (error, warn, info, debug)
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
export async function logToDb(message: string, level: string = "info"): Promise<void> {
|
||||
export async function logToDb(
|
||||
message: string,
|
||||
level: string = "info",
|
||||
): Promise<void> {
|
||||
const platform = PlatformServiceFactory.getInstance();
|
||||
const todayKey = new Date().toDateString();
|
||||
const nowKey = new Date().toISOString();
|
||||
|
||||
try {
|
||||
memoryLogs.push(`${new Date().toISOString()} ${message}`);
|
||||
@@ -194,12 +196,8 @@ export async function logToDb(message: string, level: string = "info"): Promise<
|
||||
const sevenDaysAgo = new Date(
|
||||
new Date().getTime() - 7 * 24 * 60 * 60 * 1000,
|
||||
).toDateString(); // Use date string to match schema
|
||||
memoryLogs = memoryLogs.filter(
|
||||
(log) => log.split(" ")[0] > sevenDaysAgo,
|
||||
);
|
||||
await platform.dbExec("DELETE FROM logs WHERE date < ?", [
|
||||
sevenDaysAgo,
|
||||
]);
|
||||
memoryLogs = memoryLogs.filter((log) => log.split(" ")[0] > sevenDaysAgo);
|
||||
await platform.dbExec("DELETE FROM logs WHERE date < ?", [sevenDaysAgo]);
|
||||
lastCleanupDate = todayKey;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -126,11 +126,11 @@ class MigrationRegistry {
|
||||
* ```
|
||||
*/
|
||||
registerMigration(migration: Migration): void {
|
||||
if (!migration.name || migration.name.trim() === '') {
|
||||
throw new Error('Migration name cannot be empty');
|
||||
if (!migration.name || migration.name.trim() === "") {
|
||||
throw new Error("Migration name cannot be empty");
|
||||
}
|
||||
|
||||
if (this.migrations.some(m => m.name === migration.name)) {
|
||||
if (this.migrations.some((m) => m.name === migration.name)) {
|
||||
throw new Error(`Migration with name '${migration.name}' already exists`);
|
||||
}
|
||||
|
||||
@@ -233,36 +233,54 @@ async function validateMigrationApplication<T>(
|
||||
isValid: true,
|
||||
tableExists: false,
|
||||
hasExpectedColumns: false,
|
||||
errors: []
|
||||
errors: [],
|
||||
};
|
||||
|
||||
try {
|
||||
if (migration.name === "001_initial") {
|
||||
// Validate core tables exist for initial migration
|
||||
const tables = ['accounts', 'secret', 'settings', 'contacts', 'logs', 'temp'];
|
||||
const tables = [
|
||||
"accounts",
|
||||
"secret",
|
||||
"settings",
|
||||
"contacts",
|
||||
"logs",
|
||||
"temp",
|
||||
];
|
||||
|
||||
for (const tableName of tables) {
|
||||
try {
|
||||
await sqlQuery(`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`);
|
||||
console.log(`✅ [Migration-Validation] Table ${tableName} exists`);
|
||||
await sqlQuery(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`,
|
||||
);
|
||||
logger.log(`✅ [Migration-Validation] Table ${tableName} exists`);
|
||||
} catch (error) {
|
||||
validation.isValid = false;
|
||||
validation.errors.push(`Table ${tableName} missing`);
|
||||
console.error(`❌ [Migration-Validation] Table ${tableName} missing:`, error);
|
||||
logger.error(
|
||||
`❌ [Migration-Validation] Table ${tableName} missing:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
validation.tableExists = validation.errors.length === 0;
|
||||
|
||||
} else if (migration.name === "002_add_iViewContent_to_contacts") {
|
||||
// Validate iViewContent column exists in contacts table
|
||||
try {
|
||||
await sqlQuery(`SELECT iViewContent FROM contacts LIMIT 1`);
|
||||
validation.hasExpectedColumns = true;
|
||||
console.log(`✅ [Migration-Validation] Column iViewContent exists in contacts table`);
|
||||
logger.log(
|
||||
`✅ [Migration-Validation] Column iViewContent exists in contacts table`,
|
||||
);
|
||||
} catch (error) {
|
||||
validation.isValid = false;
|
||||
validation.errors.push(`Column iViewContent missing from contacts table`);
|
||||
console.error(`❌ [Migration-Validation] Column iViewContent missing:`, error);
|
||||
validation.errors.push(
|
||||
`Column iViewContent missing from contacts table`,
|
||||
);
|
||||
logger.error(
|
||||
`❌ [Migration-Validation] Column iViewContent missing:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,11 +288,13 @@ async function validateMigrationApplication<T>(
|
||||
// } else if (migration.name === "003_future_migration") {
|
||||
// // Validate future migration schema changes
|
||||
// }
|
||||
|
||||
} catch (error) {
|
||||
validation.isValid = false;
|
||||
validation.errors.push(`Validation error: ${error}`);
|
||||
console.error(`❌ [Migration-Validation] Validation failed for ${migration.name}:`, error);
|
||||
logger.error(
|
||||
`❌ [Migration-Validation] Validation failed for ${migration.name}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
return validation;
|
||||
@@ -307,19 +327,24 @@ async function isSchemaAlreadyPresent<T>(
|
||||
try {
|
||||
if (migration.name === "001_initial") {
|
||||
// Check if accounts table exists (primary indicator of initial migration)
|
||||
const result = await sqlQuery(`SELECT name FROM sqlite_master WHERE type='table' AND name='accounts'`) as any;
|
||||
const hasTable = result?.values?.length > 0 || (Array.isArray(result) && result.length > 0);
|
||||
console.log(`🔍 [Migration-Schema] Initial migration schema check - accounts table exists: ${hasTable}`);
|
||||
const result = (await sqlQuery(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='accounts'`,
|
||||
)) as unknown as { values: unknown[][] };
|
||||
const hasTable =
|
||||
result?.values?.length > 0 ||
|
||||
(Array.isArray(result) && result.length > 0);
|
||||
logger.log(
|
||||
`🔍 [Migration-Schema] Initial migration schema check - accounts table exists: ${hasTable}`,
|
||||
);
|
||||
return hasTable;
|
||||
|
||||
} else if (migration.name === "002_add_iViewContent_to_contacts") {
|
||||
// Check if iViewContent column exists in contacts table
|
||||
try {
|
||||
await sqlQuery(`SELECT iViewContent FROM contacts LIMIT 1`);
|
||||
console.log(`🔍 [Migration-Schema] iViewContent column already exists`);
|
||||
logger.log(`🔍 [Migration-Schema] iViewContent column already exists`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.log(`🔍 [Migration-Schema] iViewContent column does not exist`);
|
||||
logger.log(`🔍 [Migration-Schema] iViewContent column does not exist`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -328,9 +353,11 @@ async function isSchemaAlreadyPresent<T>(
|
||||
// } else if (migration.name === "003_future_migration") {
|
||||
// // Check if future migration schema already exists
|
||||
// }
|
||||
|
||||
} catch (error) {
|
||||
console.log(`🔍 [Migration-Schema] Schema check failed for ${migration.name}, assuming not present:`, error);
|
||||
logger.log(
|
||||
`🔍 [Migration-Schema] Schema check failed for ${migration.name}, assuming not present:`,
|
||||
error,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -382,47 +409,56 @@ export async function runMigrations<T>(
|
||||
extractMigrationNames: (result: T) => Set<string>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
console.log("📋 [Migration] Starting migration process...");
|
||||
logger.log("📋 [Migration] Starting migration process...");
|
||||
|
||||
// Step 1: Create migrations table if it doesn't exist
|
||||
// Note: We use IF NOT EXISTS here because this is infrastructure, not a business migration
|
||||
console.log("🔧 [Migration] Creating migrations table if it doesn't exist...");
|
||||
logger.log(
|
||||
"🔧 [Migration] Creating migrations table if it doesn't exist...",
|
||||
);
|
||||
await sqlExec(`
|
||||
CREATE TABLE IF NOT EXISTS migrations (
|
||||
name TEXT PRIMARY KEY,
|
||||
applied_at TEXT DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`);
|
||||
console.log("✅ [Migration] Migrations table ready");
|
||||
logger.log("✅ [Migration] Migrations table ready");
|
||||
|
||||
// Step 2: Get list of already applied migrations
|
||||
console.log("🔍 [Migration] Querying existing migrations...");
|
||||
logger.log("🔍 [Migration] Querying existing migrations...");
|
||||
const appliedMigrationsResult = await sqlQuery(
|
||||
"SELECT name FROM migrations",
|
||||
);
|
||||
console.log("📊 [Migration] Raw query result:", appliedMigrationsResult);
|
||||
logger.log("📊 [Migration] Raw query result:", appliedMigrationsResult);
|
||||
|
||||
const appliedMigrations = extractMigrationNames(appliedMigrationsResult);
|
||||
console.log("📋 [Migration] Extracted applied migrations:", Array.from(appliedMigrations));
|
||||
logger.log(
|
||||
"📋 [Migration] Extracted applied migrations:",
|
||||
Array.from(appliedMigrations),
|
||||
);
|
||||
|
||||
// Step 3: Get all registered migrations
|
||||
const migrations = migrationRegistry.getMigrations();
|
||||
|
||||
if (migrations.length === 0) {
|
||||
console.log("⚠️ [Migration] No migrations registered");
|
||||
logger.warn("⚠️ [Migration] No migrations registered");
|
||||
logger.warn("[MigrationService] No migrations registered");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📊 [Migration] Found ${migrations.length} total migrations, ${appliedMigrations.size} already applied`);
|
||||
console.log(`📝 [Migration] Registered migrations: ${migrations.map(m => m.name).join(', ')}`);
|
||||
logger.log(
|
||||
`📊 [Migration] Found ${migrations.length} total migrations, ${appliedMigrations.size} already applied`,
|
||||
);
|
||||
logger.log(
|
||||
`📝 [Migration] Registered migrations: ${migrations.map((m) => m.name).join(", ")}`,
|
||||
);
|
||||
|
||||
let appliedCount = 0;
|
||||
let skippedCount = 0;
|
||||
|
||||
// Step 4: Process each migration
|
||||
for (const migration of migrations) {
|
||||
console.log(`\n🔍 [Migration] Processing migration: ${migration.name}`);
|
||||
logger.log(`\n🔍 [Migration] Processing migration: ${migration.name}`);
|
||||
|
||||
// Check 1: Is it recorded as applied in migrations table?
|
||||
const isRecordedAsApplied = appliedMigrations.has(migration.name);
|
||||
@@ -430,64 +466,88 @@ export async function runMigrations<T>(
|
||||
// Check 2: Does the schema already exist in the database?
|
||||
const isSchemaPresent = await isSchemaAlreadyPresent(migration, sqlQuery);
|
||||
|
||||
console.log(`🔍 [Migration] ${migration.name} - Recorded: ${isRecordedAsApplied}, Schema: ${isSchemaPresent}`);
|
||||
logger.log(
|
||||
`🔍 [Migration] ${migration.name} - Recorded: ${isRecordedAsApplied}, Schema: ${isSchemaPresent}`,
|
||||
);
|
||||
|
||||
// Skip if already recorded as applied
|
||||
if (isRecordedAsApplied) {
|
||||
console.log(`⏭️ [Migration] Skipping already applied: ${migration.name}`);
|
||||
logger.log(
|
||||
`⏭️ [Migration] Skipping already applied: ${migration.name}`,
|
||||
);
|
||||
skippedCount++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Handle case where schema exists but isn't recorded
|
||||
if (isSchemaPresent) {
|
||||
console.log(`🔄 [Migration] Schema exists but not recorded. Marking ${migration.name} as applied...`);
|
||||
logger.log(
|
||||
`🔄 [Migration] Schema exists but not recorded. Marking ${migration.name} as applied...`,
|
||||
);
|
||||
try {
|
||||
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||
migration.name,
|
||||
]);
|
||||
console.log(`✅ [Migration] Migration record inserted:`, insertResult);
|
||||
console.log(`✅ [Migration] Marked existing schema as applied: ${migration.name}`);
|
||||
const insertResult = await sqlExec(
|
||||
"INSERT INTO migrations (name) VALUES (?)",
|
||||
[migration.name],
|
||||
);
|
||||
logger.log(`✅ [Migration] Migration record inserted:`, insertResult);
|
||||
logger.log(
|
||||
`✅ [Migration] Marked existing schema as applied: ${migration.name}`,
|
||||
);
|
||||
skippedCount++;
|
||||
continue;
|
||||
} catch (insertError) {
|
||||
console.warn(`⚠️ [Migration] Could not record existing schema ${migration.name}:`, insertError);
|
||||
logger.warn(
|
||||
`⚠️ [Migration] Could not record existing schema ${migration.name}:`,
|
||||
insertError,
|
||||
);
|
||||
// Continue with normal migration process as fallback
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the migration
|
||||
console.log(`🔄 [Migration] Applying migration: ${migration.name}`);
|
||||
logger.log(`🔄 [Migration] Applying migration: ${migration.name}`);
|
||||
|
||||
try {
|
||||
// Execute the migration SQL
|
||||
console.log(`🔧 [Migration] Executing SQL for ${migration.name}...`);
|
||||
logger.log(`🔧 [Migration] Executing SQL for ${migration.name}...`);
|
||||
await sqlExec(migration.sql);
|
||||
console.log(`✅ [Migration] SQL executed successfully for ${migration.name}`);
|
||||
logger.log(
|
||||
`✅ [Migration] SQL executed successfully for ${migration.name}`,
|
||||
);
|
||||
|
||||
// Validate the migration was applied correctly
|
||||
const validation = await validateMigrationApplication(migration, sqlQuery);
|
||||
const validation = await validateMigrationApplication(
|
||||
migration,
|
||||
sqlQuery,
|
||||
);
|
||||
if (!validation.isValid) {
|
||||
console.warn(`⚠️ [Migration] Validation failed for ${migration.name}:`, validation.errors);
|
||||
logger.warn(
|
||||
`⚠️ [Migration] Validation failed for ${migration.name}:`,
|
||||
validation.errors,
|
||||
);
|
||||
} else {
|
||||
console.log(`✅ [Migration] Schema validation passed for ${migration.name}`);
|
||||
logger.log(
|
||||
`✅ [Migration] Schema validation passed for ${migration.name}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Record that the migration was applied
|
||||
console.log(`📝 [Migration] Recording migration ${migration.name} as applied...`);
|
||||
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||
migration.name,
|
||||
]);
|
||||
console.log(`✅ [Migration] Migration record inserted:`, insertResult);
|
||||
logger.log(
|
||||
`📝 [Migration] Recording migration ${migration.name} as applied...`,
|
||||
);
|
||||
const insertResult = await sqlExec(
|
||||
"INSERT INTO migrations (name) VALUES (?)",
|
||||
[migration.name],
|
||||
);
|
||||
logger.log(`✅ [Migration] Migration record inserted:`, insertResult);
|
||||
|
||||
console.log(`🎉 [Migration] Successfully applied: ${migration.name}`);
|
||||
logger.log(`🎉 [Migration] Successfully applied: ${migration.name}`);
|
||||
logger.info(
|
||||
`[MigrationService] Successfully applied migration: ${migration.name}`,
|
||||
);
|
||||
appliedCount++;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ [Migration] Error applying ${migration.name}:`, error);
|
||||
logger.error(`❌ [Migration] Error applying ${migration.name}:`, error);
|
||||
|
||||
// Handle specific cases where the migration might be partially applied
|
||||
const errorMessage = String(error).toLowerCase();
|
||||
@@ -497,33 +557,53 @@ export async function runMigrations<T>(
|
||||
errorMessage.includes("duplicate column") ||
|
||||
errorMessage.includes("column already exists") ||
|
||||
errorMessage.includes("already exists") ||
|
||||
errorMessage.includes("table") && errorMessage.includes("already exists")
|
||||
(errorMessage.includes("table") &&
|
||||
errorMessage.includes("already exists"))
|
||||
) {
|
||||
console.log(`⚠️ [Migration] ${migration.name} appears already applied (${errorMessage}). Validating and marking as complete.`);
|
||||
logger.log(
|
||||
`⚠️ [Migration] ${migration.name} appears already applied (${errorMessage}). Validating and marking as complete.`,
|
||||
);
|
||||
|
||||
// Validate the existing schema
|
||||
const validation = await validateMigrationApplication(migration, sqlQuery);
|
||||
const validation = await validateMigrationApplication(
|
||||
migration,
|
||||
sqlQuery,
|
||||
);
|
||||
if (validation.isValid) {
|
||||
console.log(`✅ [Migration] Schema validation passed for ${migration.name}`);
|
||||
logger.log(
|
||||
`✅ [Migration] Schema validation passed for ${migration.name}`,
|
||||
);
|
||||
} else {
|
||||
console.warn(`⚠️ [Migration] Schema validation failed for ${migration.name}:`, validation.errors);
|
||||
logger.warn(
|
||||
`⚠️ [Migration] Schema validation failed for ${migration.name}:`,
|
||||
validation.errors,
|
||||
);
|
||||
}
|
||||
|
||||
// Mark the migration as applied since the schema change already exists
|
||||
try {
|
||||
console.log(`📝 [Migration] Attempting to record ${migration.name} as applied despite error...`);
|
||||
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [
|
||||
migration.name,
|
||||
]);
|
||||
console.log(`✅ [Migration] Migration record inserted after error:`, insertResult);
|
||||
console.log(`✅ [Migration] Marked as applied: ${migration.name}`);
|
||||
logger.log(
|
||||
`📝 [Migration] Attempting to record ${migration.name} as applied despite error...`,
|
||||
);
|
||||
const insertResult = await sqlExec(
|
||||
"INSERT INTO migrations (name) VALUES (?)",
|
||||
[migration.name],
|
||||
);
|
||||
logger.log(
|
||||
`✅ [Migration] Migration record inserted after error:`,
|
||||
insertResult,
|
||||
);
|
||||
logger.log(`✅ [Migration] Marked as applied: ${migration.name}`);
|
||||
logger.info(
|
||||
`[MigrationService] Successfully marked migration as applied: ${migration.name}`,
|
||||
);
|
||||
appliedCount++;
|
||||
} catch (insertError) {
|
||||
// If we can't insert the migration record, log it but don't fail
|
||||
console.warn(`⚠️ [Migration] Could not record ${migration.name} as applied:`, insertError);
|
||||
logger.warn(
|
||||
`⚠️ [Migration] Could not record ${migration.name} as applied:`,
|
||||
insertError,
|
||||
);
|
||||
logger.warn(
|
||||
`[MigrationService] Could not record migration ${migration.name} as applied:`,
|
||||
insertError,
|
||||
@@ -531,40 +611,55 @@ export async function runMigrations<T>(
|
||||
}
|
||||
} else {
|
||||
// For other types of errors, still fail the migration
|
||||
console.error(`❌ [Migration] Failed to apply ${migration.name}:`, error);
|
||||
logger.error(
|
||||
`[MigrationService] Failed to apply migration ${migration.name}:`,
|
||||
error,
|
||||
);
|
||||
throw new Error(`Migration ${migration.name} failed: ${error}`);
|
||||
logger.error(
|
||||
`❌ [Migration] Failed to apply ${migration.name}:`,
|
||||
error,
|
||||
);
|
||||
logger.error(
|
||||
`[MigrationService] Failed to apply migration ${migration.name}:`,
|
||||
error,
|
||||
);
|
||||
throw new Error(`Migration ${migration.name} failed: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 5: Final validation - verify all migrations are properly recorded
|
||||
console.log("\n🔍 [Migration] Final validation - checking migrations table...");
|
||||
logger.log(
|
||||
"\n🔍 [Migration] Final validation - checking migrations table...",
|
||||
);
|
||||
const finalMigrationsResult = await sqlQuery("SELECT name FROM migrations");
|
||||
const finalAppliedMigrations = extractMigrationNames(finalMigrationsResult);
|
||||
console.log("📋 [Migration] Final applied migrations:", Array.from(finalAppliedMigrations));
|
||||
logger.log(
|
||||
"📋 [Migration] Final applied migrations:",
|
||||
Array.from(finalAppliedMigrations),
|
||||
);
|
||||
|
||||
// Check that all expected migrations are recorded
|
||||
const expectedMigrations = new Set(migrations.map(m => m.name));
|
||||
const missingMigrations = [...expectedMigrations].filter(name => !finalAppliedMigrations.has(name));
|
||||
const expectedMigrations = new Set(migrations.map((m) => m.name));
|
||||
const missingMigrations = [...expectedMigrations].filter(
|
||||
(name) => !finalAppliedMigrations.has(name),
|
||||
);
|
||||
|
||||
if (missingMigrations.length > 0) {
|
||||
console.warn(`⚠️ [Migration] Missing migration records: ${missingMigrations.join(', ')}`);
|
||||
logger.warn(`[MigrationService] Missing migration records: ${missingMigrations.join(', ')}`);
|
||||
logger.warn(
|
||||
`⚠️ [Migration] Missing migration records: ${missingMigrations.join(", ")}`,
|
||||
);
|
||||
logger.warn(
|
||||
`[MigrationService] Missing migration records: ${missingMigrations.join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`\n🎉 [Migration] Migration process complete!`);
|
||||
console.log(`📊 [Migration] Summary: Applied: ${appliedCount}, Skipped: ${skippedCount}, Total: ${migrations.length}`);
|
||||
logger.info(`[MigrationService] Migration process complete. Applied: ${appliedCount}, Skipped: ${skippedCount}`);
|
||||
|
||||
logger.log(`\n🎉 [Migration] Migration process complete!`);
|
||||
logger.log(
|
||||
`📊 [Migration] Summary: Applied: ${appliedCount}, Skipped: ${skippedCount}, Total: ${migrations.length}`,
|
||||
);
|
||||
logger.info(
|
||||
`[MigrationService] Migration process complete. Applied: ${appliedCount}, Skipped: ${skippedCount}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("\n💥 [Migration] Migration process failed:", error);
|
||||
logger.error("\n💥 [Migration] Migration process failed:", error);
|
||||
logger.error("[MigrationService] Migration process failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -308,21 +308,24 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
* @param params - Optional parameters for prepared statements
|
||||
* @returns Promise resolving to execution results
|
||||
*/
|
||||
const sqlExec = async (sql: string, params?: unknown[]): Promise<capSQLiteChanges> => {
|
||||
console.log(`🔧 [CapacitorMigration] Executing SQL:`, sql);
|
||||
console.log(`📋 [CapacitorMigration] With params:`, params);
|
||||
const sqlExec = async (
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<capSQLiteChanges> => {
|
||||
logger.log(`🔧 [CapacitorMigration] Executing SQL:`, sql);
|
||||
logger.log(`📋 [CapacitorMigration] With params:`, params);
|
||||
|
||||
if (params && params.length > 0) {
|
||||
// Use run method for parameterized queries (prepared statements)
|
||||
// This is essential for proper parameter binding and SQL injection prevention
|
||||
const result = await this.db!.run(sql, params);
|
||||
console.log(`✅ [CapacitorMigration] Run result:`, result);
|
||||
logger.log(`✅ [CapacitorMigration] Run result:`, result);
|
||||
return result;
|
||||
} else {
|
||||
// Use execute method for non-parameterized queries
|
||||
// This is more efficient for simple DDL statements
|
||||
const result = await this.db!.execute(sql);
|
||||
console.log(`✅ [CapacitorMigration] Execute result:`, result);
|
||||
logger.log(`✅ [CapacitorMigration] Execute result:`, result);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@@ -338,12 +341,15 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
* @param params - Optional parameters for prepared statements
|
||||
* @returns Promise resolving to query results
|
||||
*/
|
||||
const sqlQuery = async (sql: string, params?: unknown[]): Promise<DBSQLiteValues> => {
|
||||
console.log(`🔍 [CapacitorMigration] Querying SQL:`, sql);
|
||||
console.log(`📋 [CapacitorMigration] With params:`, params);
|
||||
const sqlQuery = async (
|
||||
sql: string,
|
||||
params?: unknown[],
|
||||
): Promise<DBSQLiteValues> => {
|
||||
logger.log(`🔍 [CapacitorMigration] Querying SQL:`, sql);
|
||||
logger.log(`📋 [CapacitorMigration] With params:`, params);
|
||||
|
||||
const result = await this.db!.query(sql, params);
|
||||
console.log(`📊 [CapacitorMigration] Query result:`, result);
|
||||
logger.log(`📊 [CapacitorMigration] Query result:`, result);
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -366,31 +372,37 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
* @returns Set of migration names found in the result
|
||||
*/
|
||||
const extractMigrationNames = (result: DBSQLiteValues): Set<string> => {
|
||||
console.log(`🔍 [CapacitorMigration] Extracting migration names from:`, result);
|
||||
logger.log(
|
||||
`🔍 [CapacitorMigration] Extracting migration names from:`,
|
||||
result,
|
||||
);
|
||||
|
||||
// Handle the Capacitor SQLite result format
|
||||
const names = result.values?.map((row: any) => {
|
||||
// The row could be an object with 'name' property or an array where name is first element
|
||||
if (typeof row === 'object' && row.name !== undefined) {
|
||||
return row.name;
|
||||
} else if (Array.isArray(row) && row.length > 0) {
|
||||
return row[0];
|
||||
}
|
||||
return null;
|
||||
}).filter(name => name !== null) || [];
|
||||
const names =
|
||||
result.values
|
||||
?.map((row: unknown) => {
|
||||
// The row could be an object with 'name' property or an array where name is first element
|
||||
if (typeof row === "object" && row !== null && "name" in row) {
|
||||
return (row as { name: string }).name;
|
||||
} else if (Array.isArray(row) && row.length > 0) {
|
||||
return row[0];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((name) => name !== null) || [];
|
||||
|
||||
console.log(`📋 [CapacitorMigration] Extracted names:`, names);
|
||||
logger.log(`📋 [CapacitorMigration] Extracted names:`, names);
|
||||
return new Set(names);
|
||||
};
|
||||
|
||||
try {
|
||||
// Execute the migration process
|
||||
await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
|
||||
await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
|
||||
|
||||
// After migrations, run integrity check to verify database state
|
||||
await this.verifyDatabaseIntegrity();
|
||||
} catch (error) {
|
||||
console.error(`❌ [CapacitorMigration] Migration failed:`, error);
|
||||
logger.error(`❌ [CapacitorMigration] Migration failed:`, error);
|
||||
// Still try to verify what we have for debugging purposes
|
||||
await this.verifyDatabaseIntegrity();
|
||||
throw error;
|
||||
@@ -451,70 +463,109 @@ export class CapacitorPlatformService implements PlatformService {
|
||||
*/
|
||||
private async verifyDatabaseIntegrity(): Promise<void> {
|
||||
if (!this.db) {
|
||||
console.error(`❌ [DB-Integrity] Database not initialized`);
|
||||
logger.error(`❌ [DB-Integrity] Database not initialized`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`🔍 [DB-Integrity] Starting database integrity check...`);
|
||||
logger.log(`🔍 [DB-Integrity] Starting database integrity check...`);
|
||||
|
||||
try {
|
||||
// Step 1: Check migrations table and applied migrations
|
||||
const migrationsResult = await this.db.query("SELECT name, applied_at FROM migrations ORDER BY applied_at");
|
||||
console.log(`📊 [DB-Integrity] Applied migrations:`, migrationsResult);
|
||||
const migrationsResult = await this.db.query(
|
||||
"SELECT name, applied_at FROM migrations ORDER BY applied_at",
|
||||
);
|
||||
logger.log(`📊 [DB-Integrity] Applied migrations:`, migrationsResult);
|
||||
|
||||
// Step 2: Verify core tables exist
|
||||
const coreTableNames = ['accounts', 'secret', 'settings', 'contacts', 'logs', 'temp'];
|
||||
const coreTableNames = [
|
||||
"accounts",
|
||||
"secret",
|
||||
"settings",
|
||||
"contacts",
|
||||
"logs",
|
||||
"temp",
|
||||
];
|
||||
const existingTables: string[] = [];
|
||||
|
||||
for (const tableName of coreTableNames) {
|
||||
try {
|
||||
const tableCheck = await this.db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`);
|
||||
const tableCheck = await this.db.query(
|
||||
`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`,
|
||||
);
|
||||
if (tableCheck.values && tableCheck.values.length > 0) {
|
||||
existingTables.push(tableName);
|
||||
console.log(`✅ [DB-Integrity] Table ${tableName} exists`);
|
||||
logger.log(`✅ [DB-Integrity] Table ${tableName} exists`);
|
||||
} else {
|
||||
console.error(`❌ [DB-Integrity] Table ${tableName} missing`);
|
||||
logger.error(`❌ [DB-Integrity] Table ${tableName} missing`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [DB-Integrity] Error checking table ${tableName}:`, error);
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] Error checking table ${tableName}:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: Check contacts table schema (including iViewContent column)
|
||||
if (existingTables.includes('contacts')) {
|
||||
if (existingTables.includes("contacts")) {
|
||||
try {
|
||||
const contactsSchema = await this.db.query("PRAGMA table_info(contacts)");
|
||||
console.log(`📊 [DB-Integrity] Contacts table schema:`, contactsSchema);
|
||||
const contactsSchema = await this.db.query(
|
||||
"PRAGMA table_info(contacts)",
|
||||
);
|
||||
logger.log(
|
||||
`📊 [DB-Integrity] Contacts table schema:`,
|
||||
contactsSchema,
|
||||
);
|
||||
|
||||
// Check for iViewContent column specifically
|
||||
const hasIViewContent = contactsSchema.values?.some((col: any) =>
|
||||
(col.name === 'iViewContent') || (Array.isArray(col) && col[1] === 'iViewContent')
|
||||
const hasIViewContent = contactsSchema.values?.some(
|
||||
(col: unknown) =>
|
||||
(typeof col === "object" &&
|
||||
col !== null &&
|
||||
"name" in col &&
|
||||
(col as { name: string }).name === "iViewContent") ||
|
||||
(Array.isArray(col) && col[1] === "iViewContent"),
|
||||
);
|
||||
|
||||
if (hasIViewContent) {
|
||||
console.log(`✅ [DB-Integrity] iViewContent column exists in contacts table`);
|
||||
logger.log(
|
||||
`✅ [DB-Integrity] iViewContent column exists in contacts table`,
|
||||
);
|
||||
} else {
|
||||
console.error(`❌ [DB-Integrity] iViewContent column missing from contacts table`);
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] iViewContent column missing from contacts table`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ [DB-Integrity] Error checking contacts schema:`, error);
|
||||
logger.error(
|
||||
`❌ [DB-Integrity] Error checking contacts schema:`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Check for basic data integrity
|
||||
try {
|
||||
const accountCount = await this.db.query("SELECT COUNT(*) as count FROM accounts");
|
||||
const settingsCount = await this.db.query("SELECT COUNT(*) as count FROM settings");
|
||||
const contactsCount = await this.db.query("SELECT COUNT(*) as count FROM contacts");
|
||||
const accountCount = await this.db.query(
|
||||
"SELECT COUNT(*) as count FROM accounts",
|
||||
);
|
||||
const settingsCount = await this.db.query(
|
||||
"SELECT COUNT(*) as count FROM settings",
|
||||
);
|
||||
const contactsCount = await this.db.query(
|
||||
"SELECT COUNT(*) as count FROM contacts",
|
||||
);
|
||||
|
||||
console.log(`📊 [DB-Integrity] Data counts - Accounts: ${JSON.stringify(accountCount)}, Settings: ${JSON.stringify(settingsCount)}, Contacts: ${JSON.stringify(contactsCount)}`);
|
||||
logger.log(
|
||||
`📊 [DB-Integrity] Data counts - Accounts: ${JSON.stringify(accountCount)}, Settings: ${JSON.stringify(settingsCount)}, Contacts: ${JSON.stringify(contactsCount)}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`❌ [DB-Integrity] Error checking data counts:`, error);
|
||||
logger.error(`❌ [DB-Integrity] Error checking data counts:`, error);
|
||||
}
|
||||
|
||||
console.log(`✅ [DB-Integrity] Database integrity check completed`);
|
||||
logger.log(`✅ [DB-Integrity] Database integrity check completed`);
|
||||
} catch (error) {
|
||||
console.error(`❌ [DB-Integrity] Database integrity check failed:`, error);
|
||||
logger.error(`❌ [DB-Integrity] Database integrity check failed:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user