forked from jsnbuchanan/crowd-funder-for-time-pwa
feat: Enhance database migration system with better logging and schema detection
- Add comprehensive console logging for Electron with emojis for better visibility - Use CREATE TABLE IF NOT EXISTS and INSERT OR IGNORE to prevent duplicate creation errors - Add specialized column existence checking for ALTER TABLE ADD COLUMN operations - Improve migration tracking with detailed status reporting (applied/skipped counts) - Add proper error handling for existing schema scenarios - Enhanced documentation and type safety for migration system This resolves issues where migrations would fail with 'table already exists' or 'duplicate column' errors when the database schema was already properly set up. The enhanced logging makes it clear to users when migrations are being skipped vs. applied, improving the debugging experience in Electron.
This commit is contained in:
@@ -1,3 +1,12 @@
|
||||
/**
|
||||
* Database Migration System for TimeSafari
|
||||
*
|
||||
* This module manages database schema migrations as users upgrade their app.
|
||||
* It ensures that database changes are applied safely and only when needed.
|
||||
*
|
||||
* @author Matthew Raymer
|
||||
*/
|
||||
|
||||
import {
|
||||
registerMigration,
|
||||
runMigrations as runMigrationsService,
|
||||
@@ -35,6 +44,7 @@ const MIGRATIONS = [
|
||||
{
|
||||
name: "001_initial",
|
||||
sql: `
|
||||
-- Create accounts table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
dateCreated TEXT NOT NULL,
|
||||
@@ -48,13 +58,16 @@ const MIGRATIONS = [
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_accounts_did ON accounts(did);
|
||||
|
||||
-- Create secret table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS secret (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
secretBase64 TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Insert secret only if table is empty
|
||||
INSERT OR IGNORE INTO secret (id, secretBase64) VALUES (1, '${secretBase64}');
|
||||
|
||||
-- Create settings table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
accountDid TEXT,
|
||||
@@ -89,8 +102,10 @@ const MIGRATIONS = [
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_settings_accountDid ON settings(accountDid);
|
||||
|
||||
-- Insert default settings only if table is empty
|
||||
INSERT OR IGNORE INTO settings (id, apiServer) VALUES (1, '${DEFAULT_ENDORSER_API_SERVER}');
|
||||
|
||||
-- Create contacts table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS contacts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
did TEXT NOT NULL,
|
||||
@@ -107,11 +122,13 @@ const MIGRATIONS = [
|
||||
CREATE INDEX IF NOT EXISTS idx_contacts_did ON contacts(did);
|
||||
CREATE INDEX IF NOT EXISTS idx_contacts_name ON contacts(name);
|
||||
|
||||
-- Create logs table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
date TEXT NOT NULL,
|
||||
message TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- Create temp table only if it doesn't exist
|
||||
CREATE TABLE IF NOT EXISTS temp (
|
||||
id TEXT PRIMARY KEY,
|
||||
blobB64 TEXT
|
||||
@@ -121,27 +138,41 @@ const MIGRATIONS = [
|
||||
{
|
||||
name: "002_add_iViewContent_to_contacts",
|
||||
sql: `
|
||||
-- We need to handle the case where iViewContent column might already exist
|
||||
-- SQLite doesn't support IF NOT EXISTS for ALTER TABLE ADD COLUMN
|
||||
-- So we'll use a more robust approach with error handling in the migration service
|
||||
|
||||
-- First, try to add the column - this will fail silently if it already exists
|
||||
-- Add iViewContent column to contacts table
|
||||
-- The migration service will check if the column already exists
|
||||
ALTER TABLE contacts ADD COLUMN iViewContent BOOLEAN DEFAULT TRUE;
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* Runs all registered database migrations
|
||||
*
|
||||
* This function ensures that the database schema is up-to-date by running
|
||||
* all pending migrations. It uses the migration service to track which
|
||||
* migrations have been applied and avoid running them multiple times.
|
||||
*
|
||||
* @param sqlExec - A function that executes a SQL statement and returns the result
|
||||
* @param extractMigrationNames - A function that extracts the names (string array) from "select name from migrations"
|
||||
* @param sqlQuery - A function that executes a SQL query and returns the result
|
||||
* @param extractMigrationNames - A function that extracts migration names from query results
|
||||
* @returns Promise that resolves when all migrations are complete
|
||||
*/
|
||||
export async function runMigrations<T>(
|
||||
sqlExec: (sql: string, params?: unknown[]) => Promise<unknown>,
|
||||
sqlQuery: (sql: string, params?: unknown[]) => Promise<T>,
|
||||
extractMigrationNames: (result: T) => Set<string>,
|
||||
): Promise<void> {
|
||||
console.log("🔄 [Migration] Starting database migration process...");
|
||||
|
||||
for (const migration of MIGRATIONS) {
|
||||
registerMigration(migration);
|
||||
}
|
||||
await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
|
||||
|
||||
try {
|
||||
await runMigrationsService(sqlExec, sqlQuery, extractMigrationNames);
|
||||
console.log("✅ [Migration] Database migration process completed successfully");
|
||||
} catch (error) {
|
||||
console.error("❌ [Migration] Database migration process failed:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user