Browse Source

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
streamline-attempt
Matthew Raymer 4 days ago
parent
commit
8868465216
  1. 14
      src/db/databaseUtil.ts
  2. 371
      src/services/migrationService.ts
  3. 233
      src/services/platforms/CapacitorPlatformService.ts

14
src/db/databaseUtil.ts

@ -175,10 +175,12 @@ export let memoryLogs: string[] = [];
* @param level - The log level (error, warn, info, debug) * @param level - The log level (error, warn, info, debug)
* @author Matthew Raymer * @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 platform = PlatformServiceFactory.getInstance();
const todayKey = new Date().toDateString(); const todayKey = new Date().toDateString();
const nowKey = new Date().toISOString();
try { try {
memoryLogs.push(`${new Date().toISOString()} ${message}`); memoryLogs.push(`${new Date().toISOString()} ${message}`);
@ -194,12 +196,8 @@ export async function logToDb(message: string, level: string = "info"): Promise<
const sevenDaysAgo = new Date( const sevenDaysAgo = new Date(
new Date().getTime() - 7 * 24 * 60 * 60 * 1000, new Date().getTime() - 7 * 24 * 60 * 60 * 1000,
).toDateString(); // Use date string to match schema ).toDateString(); // Use date string to match schema
memoryLogs = memoryLogs.filter( memoryLogs = memoryLogs.filter((log) => log.split(" ")[0] > sevenDaysAgo);
(log) => log.split(" ")[0] > sevenDaysAgo, await platform.dbExec("DELETE FROM logs WHERE date < ?", [sevenDaysAgo]);
);
await platform.dbExec("DELETE FROM logs WHERE date < ?", [
sevenDaysAgo,
]);
lastCleanupDate = todayKey; lastCleanupDate = todayKey;
} }
} catch (error) { } catch (error) {

371
src/services/migrationService.ts

@ -1,23 +1,23 @@
/** /**
* Database Migration Service for TimeSafari * Database Migration Service for TimeSafari
* *
* This module provides a comprehensive database migration system that manages * This module provides a comprehensive database migration system that manages
* schema changes as users upgrade their TimeSafari application over time. * schema changes as users upgrade their TimeSafari application over time.
* The system ensures that database changes are applied safely, tracked properly, * The system ensures that database changes are applied safely, tracked properly,
* and can handle edge cases gracefully. * and can handle edge cases gracefully.
* *
* ## Architecture Overview * ## Architecture Overview
* *
* The migration system follows these key principles: * The migration system follows these key principles:
* *
* 1. **Single Application**: Each migration runs exactly once per database * 1. **Single Application**: Each migration runs exactly once per database
* 2. **Tracked Execution**: All applied migrations are recorded in a migrations table * 2. **Tracked Execution**: All applied migrations are recorded in a migrations table
* 3. **Schema Validation**: Actual database schema is validated before and after migrations * 3. **Schema Validation**: Actual database schema is validated before and after migrations
* 4. **Graceful Recovery**: Handles cases where schema exists but tracking is missing * 4. **Graceful Recovery**: Handles cases where schema exists but tracking is missing
* 5. **Comprehensive Logging**: Detailed logging for debugging and monitoring * 5. **Comprehensive Logging**: Detailed logging for debugging and monitoring
* *
* ## Migration Flow * ## Migration Flow
* *
* ``` * ```
* 1. Create migrations table (if needed) * 1. Create migrations table (if needed)
* 2. Query existing applied migrations * 2. Query existing applied migrations
@ -30,28 +30,28 @@
* f. Record migration as applied * f. Record migration as applied
* 4. Final validation of all migrations * 4. Final validation of all migrations
* ``` * ```
* *
* ## Usage Example * ## Usage Example
* *
* ```typescript * ```typescript
* // Register migrations (typically in migration.ts) * // Register migrations (typically in migration.ts)
* registerMigration({ * registerMigration({
* name: "001_initial", * name: "001_initial",
* sql: "CREATE TABLE accounts (id INTEGER PRIMARY KEY, ...)" * sql: "CREATE TABLE accounts (id INTEGER PRIMARY KEY, ...)"
* }); * });
* *
* // Run migrations (typically in platform service) * // Run migrations (typically in platform service)
* await runMigrations(sqlExec, sqlQuery, extractMigrationNames); * await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
* ``` * ```
* *
* ## Error Handling * ## Error Handling
* *
* The system handles several error scenarios: * The system handles several error scenarios:
* - Duplicate table/column errors (schema already exists) * - Duplicate table/column errors (schema already exists)
* - Migration tracking inconsistencies * - Migration tracking inconsistencies
* - Database connection issues * - Database connection issues
* - Schema validation failures * - Schema validation failures
* *
* @author Matthew Raymer * @author Matthew Raymer
* @version 1.0.0 * @version 1.0.0
* @since 2025-06-30 * @since 2025-06-30
@ -61,11 +61,11 @@ import { logger } from "../utils/logger";
/** /**
* Migration interface for database schema migrations * Migration interface for database schema migrations
* *
* Represents a single database migration that can be applied to upgrade * Represents a single database migration that can be applied to upgrade
* the database schema. Each migration should be idempotent and focused * the database schema. Each migration should be idempotent and focused
* on a single schema change. * on a single schema change.
* *
* @interface Migration * @interface Migration
*/ */
interface Migration { interface Migration {
@ -77,10 +77,10 @@ interface Migration {
/** /**
* Migration validation result * Migration validation result
* *
* Contains the results of validating that a migration was successfully * Contains the results of validating that a migration was successfully
* applied by checking the actual database schema. * applied by checking the actual database schema.
* *
* @interface MigrationValidation * @interface MigrationValidation
*/ */
interface MigrationValidation { interface MigrationValidation {
@ -96,11 +96,11 @@ interface MigrationValidation {
/** /**
* Migration registry to store and manage database migrations * Migration registry to store and manage database migrations
* *
* This class maintains a registry of all migrations that need to be applied * This class maintains a registry of all migrations that need to be applied
* to the database. It uses the singleton pattern to ensure migrations are * to the database. It uses the singleton pattern to ensure migrations are
* registered once and can be accessed globally. * registered once and can be accessed globally.
* *
* @class MigrationRegistry * @class MigrationRegistry
*/ */
class MigrationRegistry { class MigrationRegistry {
@ -109,14 +109,14 @@ class MigrationRegistry {
/** /**
* Register a migration with the registry * Register a migration with the registry
* *
* Adds a migration to the list of migrations that will be applied when * Adds a migration to the list of migrations that will be applied when
* runMigrations() is called. Migrations should be registered in order * runMigrations() is called. Migrations should be registered in order
* of their intended execution. * of their intended execution.
* *
* @param migration - The migration to register * @param migration - The migration to register
* @throws {Error} If migration name is empty or already exists * @throws {Error} If migration name is empty or already exists
* *
* @example * @example
* ```typescript * ```typescript
* registry.registerMigration({ * registry.registerMigration({
@ -126,14 +126,14 @@ class MigrationRegistry {
* ``` * ```
*/ */
registerMigration(migration: Migration): void { registerMigration(migration: Migration): void {
if (!migration.name || migration.name.trim() === '') { if (!migration.name || migration.name.trim() === "") {
throw new Error('Migration name cannot be empty'); 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`); throw new Error(`Migration with name '${migration.name}' already exists`);
} }
this.migrations.push(migration); this.migrations.push(migration);
} }
@ -142,7 +142,7 @@ class MigrationRegistry {
* *
* Returns a copy of all migrations that have been registered with this * Returns a copy of all migrations that have been registered with this
* registry. The migrations are returned in the order they were registered. * registry. The migrations are returned in the order they were registered.
* *
* @returns Array of registered migrations (defensive copy) * @returns Array of registered migrations (defensive copy)
*/ */
getMigrations(): Migration[] { getMigrations(): Migration[] {
@ -151,10 +151,10 @@ class MigrationRegistry {
/** /**
* Clear all registered migrations * Clear all registered migrations
* *
* Removes all migrations from the registry. This is primarily used for * Removes all migrations from the registry. This is primarily used for
* testing purposes to ensure a clean state between test runs. * testing purposes to ensure a clean state between test runs.
* *
* @internal Used primarily for testing * @internal Used primarily for testing
*/ */
clearMigrations(): void { clearMigrations(): void {
@ -163,7 +163,7 @@ class MigrationRegistry {
/** /**
* Get the count of registered migrations * Get the count of registered migrations
* *
* @returns Number of migrations currently registered * @returns Number of migrations currently registered
*/ */
getCount(): number { getCount(): number {
@ -183,7 +183,7 @@ const migrationRegistry = new MigrationRegistry();
* *
* @param migration - The migration to register * @param migration - The migration to register
* @throws {Error} If migration is invalid * @throws {Error} If migration is invalid
* *
* @example * @example
* ```typescript * ```typescript
* registerMigration({ * registerMigration({
@ -207,16 +207,16 @@ export function registerMigration(migration: Migration): void {
/** /**
* Validate that a migration was successfully applied by checking schema * Validate that a migration was successfully applied by checking schema
* *
* This function performs post-migration validation to ensure that the * This function performs post-migration validation to ensure that the
* expected database schema changes were actually applied. It checks for * expected database schema changes were actually applied. It checks for
* the existence of tables, columns, and other schema elements that should * the existence of tables, columns, and other schema elements that should
* have been created by the migration. * have been created by the migration.
* *
* @param migration - The migration to validate * @param migration - The migration to validate
* @param sqlQuery - Function to execute SQL queries * @param sqlQuery - Function to execute SQL queries
* @returns Promise resolving to validation results * @returns Promise resolving to validation results
* *
* @example * @example
* ```typescript * ```typescript
* const validation = await validateMigrationApplication(migration, sqlQuery); * const validation = await validateMigrationApplication(migration, sqlQuery);
@ -233,48 +233,68 @@ async function validateMigrationApplication<T>(
isValid: true, isValid: true,
tableExists: false, tableExists: false,
hasExpectedColumns: false, hasExpectedColumns: false,
errors: [] errors: [],
}; };
try { try {
if (migration.name === "001_initial") { if (migration.name === "001_initial") {
// Validate core tables exist for initial migration // 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) { for (const tableName of tables) {
try { try {
await sqlQuery(`SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`); await sqlQuery(
console.log(`✅ [Migration-Validation] Table ${tableName} exists`); `SELECT name FROM sqlite_master WHERE type='table' AND name='${tableName}'`,
);
logger.log(`✅ [Migration-Validation] Table ${tableName} exists`);
} catch (error) { } catch (error) {
validation.isValid = false; validation.isValid = false;
validation.errors.push(`Table ${tableName} missing`); 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; validation.tableExists = validation.errors.length === 0;
} else if (migration.name === "002_add_iViewContent_to_contacts") { } else if (migration.name === "002_add_iViewContent_to_contacts") {
// Validate iViewContent column exists in contacts table // Validate iViewContent column exists in contacts table
try { try {
await sqlQuery(`SELECT iViewContent FROM contacts LIMIT 1`); await sqlQuery(`SELECT iViewContent FROM contacts LIMIT 1`);
validation.hasExpectedColumns = true; 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) { } catch (error) {
validation.isValid = false; validation.isValid = false;
validation.errors.push(`Column iViewContent missing from contacts table`); validation.errors.push(
console.error(`❌ [Migration-Validation] Column iViewContent missing:`, error); `Column iViewContent missing from contacts table`,
);
logger.error(
`❌ [Migration-Validation] Column iViewContent missing:`,
error,
);
} }
} }
// Add validation for future migrations here // Add validation for future migrations here
// } else if (migration.name === "003_future_migration") { // } else if (migration.name === "003_future_migration") {
// // Validate future migration schema changes // // Validate future migration schema changes
// } // }
} catch (error) { } catch (error) {
validation.isValid = false; validation.isValid = false;
validation.errors.push(`Validation error: ${error}`); 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; return validation;
@ -282,16 +302,16 @@ async function validateMigrationApplication<T>(
/** /**
* Check if migration is already applied by examining actual schema * Check if migration is already applied by examining actual schema
* *
* This function performs schema introspection to determine if a migration * This function performs schema introspection to determine if a migration
* has already been applied, even if it's not recorded in the migrations * has already been applied, even if it's not recorded in the migrations
* table. This is useful for handling cases where the database schema exists * table. This is useful for handling cases where the database schema exists
* but the migration tracking got out of sync. * but the migration tracking got out of sync.
* *
* @param migration - The migration to check * @param migration - The migration to check
* @param sqlQuery - Function to execute SQL queries * @param sqlQuery - Function to execute SQL queries
* @returns Promise resolving to true if schema already exists * @returns Promise resolving to true if schema already exists
* *
* @example * @example
* ```typescript * ```typescript
* const schemaExists = await isSchemaAlreadyPresent(migration, sqlQuery); * const schemaExists = await isSchemaAlreadyPresent(migration, sqlQuery);
@ -307,33 +327,40 @@ async function isSchemaAlreadyPresent<T>(
try { try {
if (migration.name === "001_initial") { if (migration.name === "001_initial") {
// Check if accounts table exists (primary indicator of initial migration) // 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 result = (await sqlQuery(
const hasTable = result?.values?.length > 0 || (Array.isArray(result) && result.length > 0); `SELECT name FROM sqlite_master WHERE type='table' AND name='accounts'`,
console.log(`🔍 [Migration-Schema] Initial migration schema check - accounts table exists: ${hasTable}`); )) 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; return hasTable;
} else if (migration.name === "002_add_iViewContent_to_contacts") { } else if (migration.name === "002_add_iViewContent_to_contacts") {
// Check if iViewContent column exists in contacts table // Check if iViewContent column exists in contacts table
try { try {
await sqlQuery(`SELECT iViewContent FROM contacts LIMIT 1`); 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; return true;
} catch (error) { } catch (error) {
console.log(`🔍 [Migration-Schema] iViewContent column does not exist`); logger.log(`🔍 [Migration-Schema] iViewContent column does not exist`);
return false; return false;
} }
} }
// Add schema checks for future migrations here // Add schema checks for future migrations here
// } else if (migration.name === "003_future_migration") { // } else if (migration.name === "003_future_migration") {
// // Check if future migration schema already exists // // Check if future migration schema already exists
// } // }
} catch (error) { } 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; return false;
} }
return false; return false;
} }
@ -347,7 +374,7 @@ async function isSchemaAlreadyPresent<T>(
* 4. Validates that migrations were applied correctly * 4. Validates that migrations were applied correctly
* 5. Records successful migrations in the tracking table * 5. Records successful migrations in the tracking table
* 6. Performs final validation of the migration state * 6. Performs final validation of the migration state
* *
* The function is designed to be idempotent - it can be run multiple times * The function is designed to be idempotent - it can be run multiple times
* safely without re-applying migrations that have already been completed. * safely without re-applying migrations that have already been completed.
* *
@ -357,22 +384,22 @@ async function isSchemaAlreadyPresent<T>(
* @param extractMigrationNames - Function to extract migration names from query results * @param extractMigrationNames - Function to extract migration names from query results
* @returns Promise that resolves when all migrations are complete * @returns Promise that resolves when all migrations are complete
* @throws {Error} If any migration fails to apply * @throws {Error} If any migration fails to apply
* *
* @example * @example
* ```typescript * ```typescript
* // Platform-specific implementation * // Platform-specific implementation
* const sqlExec = async (sql: string, params?: unknown[]) => { * const sqlExec = async (sql: string, params?: unknown[]) => {
* return await db.run(sql, params); * return await db.run(sql, params);
* }; * };
* *
* const sqlQuery = async (sql: string, params?: unknown[]) => { * const sqlQuery = async (sql: string, params?: unknown[]) => {
* return await db.query(sql, params); * return await db.query(sql, params);
* }; * };
* *
* const extractNames = (result: DBResult) => { * const extractNames = (result: DBResult) => {
* return new Set(result.values.map(row => row[0])); * return new Set(result.values.map(row => row[0]));
* }; * };
* *
* await runMigrations(sqlExec, sqlQuery, extractNames); * await runMigrations(sqlExec, sqlQuery, extractNames);
* ``` * ```
*/ */
@ -382,113 +409,146 @@ export async function runMigrations<T>(
extractMigrationNames: (result: T) => Set<string>, extractMigrationNames: (result: T) => Set<string>,
): Promise<void> { ): Promise<void> {
try { try {
console.log("📋 [Migration] Starting migration process..."); logger.log("📋 [Migration] Starting migration process...");
// Step 1: Create migrations table if it doesn't exist // 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 // 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(` await sqlExec(`
CREATE TABLE IF NOT EXISTS migrations ( CREATE TABLE IF NOT EXISTS migrations (
name TEXT PRIMARY KEY, name TEXT PRIMARY KEY,
applied_at TEXT DEFAULT CURRENT_TIMESTAMP 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 // Step 2: Get list of already applied migrations
console.log("🔍 [Migration] Querying existing migrations..."); logger.log("🔍 [Migration] Querying existing migrations...");
const appliedMigrationsResult = await sqlQuery( const appliedMigrationsResult = await sqlQuery(
"SELECT name FROM migrations", "SELECT name FROM migrations",
); );
console.log("📊 [Migration] Raw query result:", appliedMigrationsResult); logger.log("📊 [Migration] Raw query result:", appliedMigrationsResult);
const appliedMigrations = extractMigrationNames(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 // Step 3: Get all registered migrations
const migrations = migrationRegistry.getMigrations(); const migrations = migrationRegistry.getMigrations();
if (migrations.length === 0) { if (migrations.length === 0) {
console.log("⚠️ [Migration] No migrations registered"); logger.warn("⚠️ [Migration] No migrations registered");
logger.warn("[MigrationService] No migrations registered"); logger.warn("[MigrationService] No migrations registered");
return; return;
} }
console.log(`📊 [Migration] Found ${migrations.length} total migrations, ${appliedMigrations.size} already applied`); logger.log(
console.log(`📝 [Migration] Registered migrations: ${migrations.map(m => m.name).join(', ')}`); `📊 [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 appliedCount = 0;
let skippedCount = 0; let skippedCount = 0;
// Step 4: Process each migration // Step 4: Process each migration
for (const migration of migrations) { 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? // Check 1: Is it recorded as applied in migrations table?
const isRecordedAsApplied = appliedMigrations.has(migration.name); const isRecordedAsApplied = appliedMigrations.has(migration.name);
// Check 2: Does the schema already exist in the database? // Check 2: Does the schema already exist in the database?
const isSchemaPresent = await isSchemaAlreadyPresent(migration, sqlQuery); 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 // Skip if already recorded as applied
if (isRecordedAsApplied) { if (isRecordedAsApplied) {
console.log(`⏭️ [Migration] Skipping already applied: ${migration.name}`); logger.log(
`⏭️ [Migration] Skipping already applied: ${migration.name}`,
);
skippedCount++; skippedCount++;
continue; continue;
} }
// Handle case where schema exists but isn't recorded // Handle case where schema exists but isn't recorded
if (isSchemaPresent) { 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 { try {
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [ const insertResult = await sqlExec(
migration.name, "INSERT INTO migrations (name) VALUES (?)",
]); [migration.name],
console.log(`✅ [Migration] Migration record inserted:`, insertResult); );
console.log(`✅ [Migration] Marked existing schema as applied: ${migration.name}`); logger.log(`✅ [Migration] Migration record inserted:`, insertResult);
logger.log(
`✅ [Migration] Marked existing schema as applied: ${migration.name}`,
);
skippedCount++; skippedCount++;
continue; continue;
} catch (insertError) { } 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 // Continue with normal migration process as fallback
} }
} }
// Apply the migration // Apply the migration
console.log(`🔄 [Migration] Applying migration: ${migration.name}`); logger.log(`🔄 [Migration] Applying migration: ${migration.name}`);
try { try {
// Execute the migration SQL // Execute the migration SQL
console.log(`🔧 [Migration] Executing SQL for ${migration.name}...`); logger.log(`🔧 [Migration] Executing SQL for ${migration.name}...`);
await sqlExec(migration.sql); 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 // Validate the migration was applied correctly
const validation = await validateMigrationApplication(migration, sqlQuery); const validation = await validateMigrationApplication(
migration,
sqlQuery,
);
if (!validation.isValid) { if (!validation.isValid) {
console.warn(`⚠️ [Migration] Validation failed for ${migration.name}:`, validation.errors); logger.warn(
`⚠️ [Migration] Validation failed for ${migration.name}:`,
validation.errors,
);
} else { } 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 // Record that the migration was applied
console.log(`📝 [Migration] Recording migration ${migration.name} as applied...`); logger.log(
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [ `📝 [Migration] Recording migration ${migration.name} as applied...`,
migration.name, );
]); const insertResult = await sqlExec(
console.log(`✅ [Migration] Migration record inserted:`, insertResult); "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( logger.info(
`[MigrationService] Successfully applied migration: ${migration.name}`, `[MigrationService] Successfully applied migration: ${migration.name}`,
); );
appliedCount++; appliedCount++;
} catch (error) { } 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 // Handle specific cases where the migration might be partially applied
const errorMessage = String(error).toLowerCase(); const errorMessage = String(error).toLowerCase();
@ -497,33 +557,53 @@ export async function runMigrations<T>(
errorMessage.includes("duplicate column") || errorMessage.includes("duplicate column") ||
errorMessage.includes("column already exists") || errorMessage.includes("column already exists") ||
errorMessage.includes("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 // Validate the existing schema
const validation = await validateMigrationApplication(migration, sqlQuery); const validation = await validateMigrationApplication(
migration,
sqlQuery,
);
if (validation.isValid) { if (validation.isValid) {
console.log(`✅ [Migration] Schema validation passed for ${migration.name}`); logger.log(
`✅ [Migration] Schema validation passed for ${migration.name}`,
);
} else { } 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 // Mark the migration as applied since the schema change already exists
try { try {
console.log(`📝 [Migration] Attempting to record ${migration.name} as applied despite error...`); logger.log(
const insertResult = await sqlExec("INSERT INTO migrations (name) VALUES (?)", [ `📝 [Migration] Attempting to record ${migration.name} as applied despite error...`,
migration.name, );
]); const insertResult = await sqlExec(
console.log(`✅ [Migration] Migration record inserted after error:`, insertResult); "INSERT INTO migrations (name) VALUES (?)",
console.log(`✅ [Migration] Marked as applied: ${migration.name}`); [migration.name],
);
logger.log(
`✅ [Migration] Migration record inserted after error:`,
insertResult,
);
logger.log(`✅ [Migration] Marked as applied: ${migration.name}`);
logger.info( logger.info(
`[MigrationService] Successfully marked migration as applied: ${migration.name}`, `[MigrationService] Successfully marked migration as applied: ${migration.name}`,
); );
appliedCount++; appliedCount++;
} catch (insertError) { } catch (insertError) {
// If we can't insert the migration record, log it but don't fail // 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( logger.warn(
`[MigrationService] Could not record migration ${migration.name} as applied:`, `[MigrationService] Could not record migration ${migration.name} as applied:`,
insertError, insertError,
@ -531,40 +611,55 @@ export async function runMigrations<T>(
} }
} else { } else {
// For other types of errors, still fail the migration // For other types of errors, still fail the migration
console.error(`❌ [Migration] Failed to apply ${migration.name}:`, error); logger.error(
logger.error( `❌ [Migration] Failed to apply ${migration.name}:`,
`[MigrationService] Failed to apply migration ${migration.name}:`, error,
error, );
); logger.error(
throw new Error(`Migration ${migration.name} failed: ${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 // 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 finalMigrationsResult = await sqlQuery("SELECT name FROM migrations");
const finalAppliedMigrations = extractMigrationNames(finalMigrationsResult); 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 // Check that all expected migrations are recorded
const expectedMigrations = new Set(migrations.map(m => m.name)); const expectedMigrations = new Set(migrations.map((m) => m.name));
const missingMigrations = [...expectedMigrations].filter(name => !finalAppliedMigrations.has(name)); const missingMigrations = [...expectedMigrations].filter(
(name) => !finalAppliedMigrations.has(name),
);
if (missingMigrations.length > 0) { if (missingMigrations.length > 0) {
console.warn(`⚠️ [Migration] Missing migration records: ${missingMigrations.join(', ')}`); logger.warn(
logger.warn(`[MigrationService] Missing migration records: ${missingMigrations.join(', ')}`); `⚠️ [Migration] Missing migration records: ${missingMigrations.join(", ")}`,
);
logger.warn(
`[MigrationService] Missing migration records: ${missingMigrations.join(", ")}`,
);
} }
console.log(`\n🎉 [Migration] Migration process complete!`); logger.log(`\n🎉 [Migration] Migration process complete!`);
console.log(`📊 [Migration] Summary: Applied: ${appliedCount}, Skipped: ${skippedCount}, Total: ${migrations.length}`); logger.log(
logger.info(`[MigrationService] Migration process complete. Applied: ${appliedCount}, Skipped: ${skippedCount}`); `📊 [Migration] Summary: Applied: ${appliedCount}, Skipped: ${skippedCount}, Total: ${migrations.length}`,
);
logger.info(
`[MigrationService] Migration process complete. Applied: ${appliedCount}, Skipped: ${skippedCount}`,
);
} catch (error) { } 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); logger.error("[MigrationService] Migration process failed:", error);
throw error; throw error;
} }
} }

233
src/services/platforms/CapacitorPlatformService.ts

@ -239,42 +239,42 @@ export class CapacitorPlatformService implements PlatformService {
/** /**
* Execute database migrations for the Capacitor platform * Execute database migrations for the Capacitor platform
* *
* This method orchestrates the database migration process specifically for * This method orchestrates the database migration process specifically for
* Capacitor-based platforms (mobile and Electron). It provides the platform-specific * Capacitor-based platforms (mobile and Electron). It provides the platform-specific
* SQL execution functions to the migration service and handles Capacitor SQLite * SQL execution functions to the migration service and handles Capacitor SQLite
* plugin integration. * plugin integration.
* *
* ## Migration Process: * ## Migration Process:
* *
* 1. **SQL Execution Setup**: Creates platform-specific SQL execution functions * 1. **SQL Execution Setup**: Creates platform-specific SQL execution functions
* that properly handle the Capacitor SQLite plugin's API * that properly handle the Capacitor SQLite plugin's API
* *
* 2. **Parameter Handling**: Ensures proper parameter binding for prepared statements * 2. **Parameter Handling**: Ensures proper parameter binding for prepared statements
* using the correct Capacitor SQLite methods (run vs execute) * using the correct Capacitor SQLite methods (run vs execute)
* *
* 3. **Result Parsing**: Provides extraction functions that understand the * 3. **Result Parsing**: Provides extraction functions that understand the
* Capacitor SQLite result format * Capacitor SQLite result format
* *
* 4. **Migration Execution**: Delegates to the migration service for the actual * 4. **Migration Execution**: Delegates to the migration service for the actual
* migration logic and tracking * migration logic and tracking
* *
* 5. **Integrity Verification**: Runs post-migration integrity checks to ensure * 5. **Integrity Verification**: Runs post-migration integrity checks to ensure
* the database is in the expected state * the database is in the expected state
* *
* ## Error Handling: * ## Error Handling:
* *
* The method includes comprehensive error handling for: * The method includes comprehensive error handling for:
* - Database connection issues * - Database connection issues
* - SQL execution failures * - SQL execution failures
* - Migration tracking problems * - Migration tracking problems
* - Schema validation errors * - Schema validation errors
* *
* Even if migrations fail, the integrity check still runs to assess the * Even if migrations fail, the integrity check still runs to assess the
* current database state and provide debugging information. * current database state and provide debugging information.
* *
* ## Logging: * ## Logging:
* *
* Detailed logging is provided throughout the process using emoji-tagged * Detailed logging is provided throughout the process using emoji-tagged
* console messages that appear in the Electron DevTools console. This * console messages that appear in the Electron DevTools console. This
* includes: * includes:
@ -282,10 +282,10 @@ export class CapacitorPlatformService implements PlatformService {
* - Parameter values for debugging * - Parameter values for debugging
* - Migration success/failure status * - Migration success/failure status
* - Database integrity check results * - Database integrity check results
* *
* @throws {Error} If database is not initialized or migrations fail critically * @throws {Error} If database is not initialized or migrations fail critically
* @private Internal method called during database initialization * @private Internal method called during database initialization
* *
* @example * @example
* ```typescript * ```typescript
* // Called automatically during platform service initialization * // Called automatically during platform service initialization
@ -299,98 +299,110 @@ export class CapacitorPlatformService implements PlatformService {
/** /**
* SQL execution function for Capacitor SQLite plugin * SQL execution function for Capacitor SQLite plugin
* *
* This function handles the execution of SQL statements (INSERT, UPDATE, CREATE, etc.) * This function handles the execution of SQL statements (INSERT, UPDATE, CREATE, etc.)
* through the Capacitor SQLite plugin. It automatically chooses the appropriate * through the Capacitor SQLite plugin. It automatically chooses the appropriate
* method based on whether parameters are provided. * method based on whether parameters are provided.
* *
* @param sql - SQL statement to execute * @param sql - SQL statement to execute
* @param params - Optional parameters for prepared statements * @param params - Optional parameters for prepared statements
* @returns Promise resolving to execution results * @returns Promise resolving to execution results
*/ */
const sqlExec = async (sql: string, params?: unknown[]): Promise<capSQLiteChanges> => { const sqlExec = async (
console.log(`🔧 [CapacitorMigration] Executing SQL:`, sql); sql: string,
console.log(`📋 [CapacitorMigration] With params:`, params); params?: unknown[],
): Promise<capSQLiteChanges> => {
logger.log(`🔧 [CapacitorMigration] Executing SQL:`, sql);
logger.log(`📋 [CapacitorMigration] With params:`, params);
if (params && params.length > 0) { if (params && params.length > 0) {
// Use run method for parameterized queries (prepared statements) // Use run method for parameterized queries (prepared statements)
// This is essential for proper parameter binding and SQL injection prevention // This is essential for proper parameter binding and SQL injection prevention
const result = await this.db!.run(sql, params); const result = await this.db!.run(sql, params);
console.log(`✅ [CapacitorMigration] Run result:`, result); logger.log(`✅ [CapacitorMigration] Run result:`, result);
return result; return result;
} else { } else {
// Use execute method for non-parameterized queries // Use execute method for non-parameterized queries
// This is more efficient for simple DDL statements // This is more efficient for simple DDL statements
const result = await this.db!.execute(sql); const result = await this.db!.execute(sql);
console.log(`✅ [CapacitorMigration] Execute result:`, result); logger.log(`✅ [CapacitorMigration] Execute result:`, result);
return result; return result;
} }
}; };
/** /**
* SQL query function for Capacitor SQLite plugin * SQL query function for Capacitor SQLite plugin
* *
* This function handles the execution of SQL queries (SELECT statements) * This function handles the execution of SQL queries (SELECT statements)
* through the Capacitor SQLite plugin. It returns the raw result data * through the Capacitor SQLite plugin. It returns the raw result data
* that can be processed by the migration service. * that can be processed by the migration service.
* *
* @param sql - SQL query to execute * @param sql - SQL query to execute
* @param params - Optional parameters for prepared statements * @param params - Optional parameters for prepared statements
* @returns Promise resolving to query results * @returns Promise resolving to query results
*/ */
const sqlQuery = async (sql: string, params?: unknown[]): Promise<DBSQLiteValues> => { const sqlQuery = async (
console.log(`🔍 [CapacitorMigration] Querying SQL:`, sql); sql: string,
console.log(`📋 [CapacitorMigration] With params:`, params); params?: unknown[],
): Promise<DBSQLiteValues> => {
logger.log(`🔍 [CapacitorMigration] Querying SQL:`, sql);
logger.log(`📋 [CapacitorMigration] With params:`, params);
const result = await this.db!.query(sql, params); const result = await this.db!.query(sql, params);
console.log(`📊 [CapacitorMigration] Query result:`, result); logger.log(`📊 [CapacitorMigration] Query result:`, result);
return result; return result;
}; };
/** /**
* Extract migration names from Capacitor SQLite query results * Extract migration names from Capacitor SQLite query results
* *
* This function parses the result format returned by the Capacitor SQLite * This function parses the result format returned by the Capacitor SQLite
* plugin and extracts migration names. It handles the specific data structure * plugin and extracts migration names. It handles the specific data structure
* used by the plugin, which can vary between different result formats. * used by the plugin, which can vary between different result formats.
* *
* ## Result Format Handling: * ## Result Format Handling:
* *
* The Capacitor SQLite plugin can return results in different formats: * The Capacitor SQLite plugin can return results in different formats:
* - Object format: `{ name: "migration_name" }` * - Object format: `{ name: "migration_name" }`
* - Array format: `["migration_name", "timestamp"]` * - Array format: `["migration_name", "timestamp"]`
* *
* This function handles both formats to ensure robust migration name extraction. * This function handles both formats to ensure robust migration name extraction.
* *
* @param result - Query result from Capacitor SQLite plugin * @param result - Query result from Capacitor SQLite plugin
* @returns Set of migration names found in the result * @returns Set of migration names found in the result
*/ */
const extractMigrationNames = (result: DBSQLiteValues): Set<string> => { 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 // Handle the Capacitor SQLite result format
const names = result.values?.map((row: any) => { const names =
// The row could be an object with 'name' property or an array where name is first element result.values
if (typeof row === 'object' && row.name !== undefined) { ?.map((row: unknown) => {
return row.name; // The row could be an object with 'name' property or an array where name is first element
} else if (Array.isArray(row) && row.length > 0) { if (typeof row === "object" && row !== null && "name" in row) {
return row[0]; return (row as { name: string }).name;
} } else if (Array.isArray(row) && row.length > 0) {
return null; return row[0];
}).filter(name => name !== null) || []; }
return null;
console.log(`📋 [CapacitorMigration] Extracted names:`, names); })
.filter((name) => name !== null) || [];
logger.log(`📋 [CapacitorMigration] Extracted names:`, names);
return new Set(names); return new Set(names);
}; };
try { try {
// Execute the migration process // Execute the migration process
await runMigrations(sqlExec, sqlQuery, extractMigrationNames); await runMigrations(sqlExec, sqlQuery, extractMigrationNames);
// After migrations, run integrity check to verify database state // After migrations, run integrity check to verify database state
await this.verifyDatabaseIntegrity(); await this.verifyDatabaseIntegrity();
} catch (error) { } catch (error) {
console.error(`❌ [CapacitorMigration] Migration failed:`, error); logger.error(`❌ [CapacitorMigration] Migration failed:`, error);
// Still try to verify what we have for debugging purposes // Still try to verify what we have for debugging purposes
await this.verifyDatabaseIntegrity(); await this.verifyDatabaseIntegrity();
throw error; throw error;
@ -399,50 +411,50 @@ export class CapacitorPlatformService implements PlatformService {
/** /**
* Verify database integrity and migration status * Verify database integrity and migration status
* *
* This method performs comprehensive validation of the database structure * This method performs comprehensive validation of the database structure
* and migration state. It's designed to help identify issues with the * and migration state. It's designed to help identify issues with the
* migration process and provide detailed debugging information. * migration process and provide detailed debugging information.
* *
* ## Validation Steps: * ## Validation Steps:
* *
* 1. **Migration Records**: Checks which migrations are recorded as applied * 1. **Migration Records**: Checks which migrations are recorded as applied
* 2. **Table Existence**: Verifies all expected core tables exist * 2. **Table Existence**: Verifies all expected core tables exist
* 3. **Schema Validation**: Checks table schemas including column presence * 3. **Schema Validation**: Checks table schemas including column presence
* 4. **Data Integrity**: Validates basic data counts and structure * 4. **Data Integrity**: Validates basic data counts and structure
* *
* ## Core Tables Validated: * ## Core Tables Validated:
* *
* - `accounts`: User identity and cryptographic keys * - `accounts`: User identity and cryptographic keys
* - `secret`: Application secrets and encryption keys * - `secret`: Application secrets and encryption keys
* - `settings`: Configuration and user preferences * - `settings`: Configuration and user preferences
* - `contacts`: Contact network and trust relationships * - `contacts`: Contact network and trust relationships
* - `logs`: Application event logging * - `logs`: Application event logging
* - `temp`: Temporary data storage * - `temp`: Temporary data storage
* *
* ## Schema Checks: * ## Schema Checks:
* *
* For critical tables like `contacts`, the method validates: * For critical tables like `contacts`, the method validates:
* - Table structure using `PRAGMA table_info` * - Table structure using `PRAGMA table_info`
* - Presence of important columns (e.g., `iViewContent`) * - Presence of important columns (e.g., `iViewContent`)
* - Column data types and constraints * - Column data types and constraints
* *
* ## Error Handling: * ## Error Handling:
* *
* This method is designed to never throw errors - it captures and logs * This method is designed to never throw errors - it captures and logs
* all validation issues for debugging purposes. This ensures that even * all validation issues for debugging purposes. This ensures that even
* if integrity checks fail, they don't prevent the application from starting. * if integrity checks fail, they don't prevent the application from starting.
* *
* ## Logging Output: * ## Logging Output:
* *
* The method produces detailed console output with emoji tags: * The method produces detailed console output with emoji tags:
* - `` for successful validations * - `` for successful validations
* - `` for validation failures * - `` for validation failures
* - `📊` for data summaries * - `📊` for data summaries
* - `🔍` for investigation steps * - `🔍` for investigation steps
* *
* @private Internal method called after migrations * @private Internal method called after migrations
* *
* @example * @example
* ```typescript * ```typescript
* // Called automatically after migration completion * // Called automatically after migration completion
@ -451,70 +463,109 @@ export class CapacitorPlatformService implements PlatformService {
*/ */
private async verifyDatabaseIntegrity(): Promise<void> { private async verifyDatabaseIntegrity(): Promise<void> {
if (!this.db) { if (!this.db) {
console.error(`❌ [DB-Integrity] Database not initialized`); logger.error(`❌ [DB-Integrity] Database not initialized`);
return; return;
} }
console.log(`🔍 [DB-Integrity] Starting database integrity check...`); logger.log(`🔍 [DB-Integrity] Starting database integrity check...`);
try { try {
// Step 1: Check migrations table and applied migrations // Step 1: Check migrations table and applied migrations
const migrationsResult = await this.db.query("SELECT name, applied_at FROM migrations ORDER BY applied_at"); const migrationsResult = await this.db.query(
console.log(`📊 [DB-Integrity] Applied migrations:`, migrationsResult); "SELECT name, applied_at FROM migrations ORDER BY applied_at",
);
logger.log(`📊 [DB-Integrity] Applied migrations:`, migrationsResult);
// Step 2: Verify core tables exist // 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[] = []; const existingTables: string[] = [];
for (const tableName of coreTableNames) { for (const tableName of coreTableNames) {
try { 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) { if (tableCheck.values && tableCheck.values.length > 0) {
existingTables.push(tableName); existingTables.push(tableName);
console.log(`✅ [DB-Integrity] Table ${tableName} exists`); logger.log(`✅ [DB-Integrity] Table ${tableName} exists`);
} else { } else {
console.error(`❌ [DB-Integrity] Table ${tableName} missing`); logger.error(`❌ [DB-Integrity] Table ${tableName} missing`);
} }
} catch (error) { } 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) // Step 3: Check contacts table schema (including iViewContent column)
if (existingTables.includes('contacts')) { if (existingTables.includes("contacts")) {
try { try {
const contactsSchema = await this.db.query("PRAGMA table_info(contacts)"); const contactsSchema = await this.db.query(
console.log(`📊 [DB-Integrity] Contacts table schema:`, contactsSchema); "PRAGMA table_info(contacts)",
);
logger.log(
`📊 [DB-Integrity] Contacts table schema:`,
contactsSchema,
);
// Check for iViewContent column specifically // Check for iViewContent column specifically
const hasIViewContent = contactsSchema.values?.some((col: any) => const hasIViewContent = contactsSchema.values?.some(
(col.name === 'iViewContent') || (Array.isArray(col) && col[1] === 'iViewContent') (col: unknown) =>
(typeof col === "object" &&
col !== null &&
"name" in col &&
(col as { name: string }).name === "iViewContent") ||
(Array.isArray(col) && col[1] === "iViewContent"),
); );
if (hasIViewContent) { if (hasIViewContent) {
console.log(`✅ [DB-Integrity] iViewContent column exists in contacts table`); logger.log(
`✅ [DB-Integrity] iViewContent column exists in contacts table`,
);
} else { } else {
console.error(`❌ [DB-Integrity] iViewContent column missing from contacts table`); logger.error(
`❌ [DB-Integrity] iViewContent column missing from contacts table`,
);
} }
} catch (error) { } 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 // Step 4: Check for basic data integrity
try { try {
const accountCount = await this.db.query("SELECT COUNT(*) as count FROM accounts"); const accountCount = await this.db.query(
const settingsCount = await this.db.query("SELECT COUNT(*) as count FROM settings"); "SELECT COUNT(*) as count FROM accounts",
const contactsCount = await this.db.query("SELECT COUNT(*) as count FROM contacts"); );
const settingsCount = await this.db.query(
console.log(`📊 [DB-Integrity] Data counts - Accounts: ${JSON.stringify(accountCount)}, Settings: ${JSON.stringify(settingsCount)}, Contacts: ${JSON.stringify(contactsCount)}`); "SELECT COUNT(*) as count FROM settings",
);
const contactsCount = await this.db.query(
"SELECT COUNT(*) as count FROM contacts",
);
logger.log(
`📊 [DB-Integrity] Data counts - Accounts: ${JSON.stringify(accountCount)}, Settings: ${JSON.stringify(settingsCount)}, Contacts: ${JSON.stringify(contactsCount)}`,
);
} catch (error) { } 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) { } catch (error) {
console.error(`❌ [DB-Integrity] Database integrity check failed:`, error); logger.error(`❌ [DB-Integrity] Database integrity check failed:`, error);
} }
} }

Loading…
Cancel
Save