Skip to content

Commit

Permalink
Merge pull request #41 from jordan-dalby/backup-database-before-migra…
Browse files Browse the repository at this point in the history
…tion

Backup database before attempting migration
  • Loading branch information
jordan-dalby authored Nov 10, 2024
2 parents dccb406 + 61dd705 commit 73e11ae
Showing 1 changed file with 84 additions and 57 deletions.
141 changes: 84 additions & 57 deletions server/src/config/database.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 (?, ?, ?, ?, ?)'
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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);
Expand Down

0 comments on commit 73e11ae

Please sign in to comment.