From 61dd70537efd7a63f41810e915af096746ad4914 Mon Sep 17 00:00:00 2001 From: Jordan Dalby Date: Sun, 10 Nov 2024 22:13:16 +0000 Subject: [PATCH] Backup database before attempting migration --- server/src/config/database.js | 141 ++++++++++++++++++++-------------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/server/src/config/database.js b/server/src/config/database.js index b570910..a881044 100644 --- a/server/src/config/database.js +++ b/server/src/config/database.js @@ -22,27 +22,45 @@ function getDatabasePath() { } } -async function migrateToFragments(db) { - console.log('Starting migration to fragments...'); +function backupDatabase(dbPath) { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const backupPath = `${dbPath}.${timestamp}.backup`; - // Check if code column exists in snippets table + try { + if (fs.existsSync(dbPath)) { + fs.copyFileSync(dbPath, backupPath); + console.log(`Database backed up to: ${backupPath}`); + return true; + } + return false; + } catch (error) { + console.error('Failed to create database backup:', error); + throw error; + } +} + +function needsMigration(db) { const hasCodeColumn = db.prepare(` SELECT COUNT(*) as count FROM pragma_table_info('snippets') WHERE name = 'code' `).get().count > 0; - if (!hasCodeColumn) { + return hasCodeColumn; +} + +async function migrateToFragments(db) { + console.log('Starting migration to fragments...'); + + if (!needsMigration(db)) { console.log('Migration already completed'); return; } - // Enable foreign keys and start transaction db.pragma('foreign_keys = OFF'); try { db.transaction(() => { - // Create fragments table with language column db.exec(` CREATE TABLE IF NOT EXISTS fragments ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -57,7 +75,6 @@ async function migrateToFragments(db) { CREATE INDEX IF NOT EXISTS idx_fragments_snippet_id ON fragments(snippet_id); `); - // Copy existing code to fragments, using the snippet's language const snippets = db.prepare('SELECT id, code, language FROM snippets').all(); const insertFragment = db.prepare( 'INSERT INTO fragments (snippet_id, file_name, code, language, position) VALUES (?, ?, ?, ?, ?)' @@ -67,7 +84,6 @@ async function migrateToFragments(db) { insertFragment.run(snippet.id, 'main', snippet.code || '', snippet.language || 'plaintext', 0); } - // Remove language and code columns from snippets table db.exec(` CREATE TABLE snippets_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -93,11 +109,55 @@ async function migrateToFragments(db) { } } +function createInitialSchema(db) { + db.exec(` + CREATE TABLE IF NOT EXISTS snippets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + language TEXT NOT NULL, + description TEXT, + updated_at DATETIME DEFAULT CURRENT_TIMESTAMP + ); + + CREATE TABLE IF NOT EXISTS categories ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snippet_id INTEGER, + name TEXT NOT NULL, + FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE + ); + + CREATE TABLE IF NOT EXISTS fragments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snippet_id INTEGER NOT NULL, + file_name TEXT NOT NULL, + code TEXT NOT NULL, + position INTEGER NOT NULL, + FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE + ); + + CREATE INDEX IF NOT EXISTS idx_categories_snippet_id ON categories(snippet_id); + CREATE INDEX IF NOT EXISTS idx_fragments_snippet_id ON fragments(snippet_id); + + CREATE TABLE IF NOT EXISTS shared_snippets ( + id TEXT PRIMARY KEY, + snippet_id INTEGER NOT NULL, + requires_auth BOOLEAN NOT NULL DEFAULT false, + expires_at DATETIME, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE + ); + + CREATE INDEX IF NOT EXISTS idx_shared_snippets_snippet_id ON shared_snippets(snippet_id); + `); +} + function initializeDatabase() { try { const dbPath = getDatabasePath(); console.log(`Initializing SQLite database at: ${dbPath}`); + const dbExists = fs.existsSync(dbPath); + db = new Database(dbPath, { verbose: console.log, fileMustExist: false @@ -106,57 +166,24 @@ function initializeDatabase() { db.pragma('foreign_keys = ON'); db.pragma('journal_mode = WAL'); - const fragmentsTable = db.prepare(` - SELECT name FROM sqlite_master - WHERE type='table' AND name='fragments' - `).get(); - - if (!fragmentsTable) { - console.log('Fragments table not found, running migration...'); - migrateToFragments(db); + if (!dbExists) { + console.log('Creating new database with initial schema...'); + createInitialSchema(db); + } else { + console.log('Database file exists, checking for needed migrations...'); + + if (needsMigration(db)) { + console.log('Database needs migration, creating backup...'); + if (backupDatabase(dbPath)) { + console.log('Starting migration process...'); + migrateToFragments(db); + } + } else { + console.log('Database schema is up to date'); + } } - db.exec(` - CREATE TABLE IF NOT EXISTS snippets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - title TEXT NOT NULL, - language TEXT NOT NULL, - description TEXT, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - ); - - CREATE TABLE IF NOT EXISTS categories ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - snippet_id INTEGER, - name TEXT NOT NULL, - FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE - ); - - CREATE TABLE IF NOT EXISTS fragments ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - snippet_id INTEGER NOT NULL, - file_name TEXT NOT NULL, - code TEXT NOT NULL, - position INTEGER NOT NULL, - FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS idx_categories_snippet_id ON categories(snippet_id); - CREATE INDEX IF NOT EXISTS idx_fragments_snippet_id ON fragments(snippet_id); - - CREATE TABLE IF NOT EXISTS shared_snippets ( - id TEXT PRIMARY KEY, - snippet_id INTEGER NOT NULL, - requires_auth BOOLEAN NOT NULL DEFAULT false, - expires_at DATETIME, - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (snippet_id) REFERENCES snippets(id) ON DELETE CASCADE - ); - - CREATE INDEX IF NOT EXISTS idx_shared_snippets_snippet_id ON shared_snippets(snippet_id); - `); - - console.log('Database initialized successfully'); + console.log('Database initialization completed successfully'); return db; } catch (error) { console.error('Database initialization error:', error);