Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backup database before attempting migration #41

Merged
merged 1 commit into from
Nov 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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