Files
Perplexica/src/lib/db/migrate.ts
2025-10-06 10:13:34 +05:30

123 lines
3.5 KiB
TypeScript

import Database from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
const DATA_DIR = process.env.DATA_DIR || process.cwd();
const dbPath = path.join(DATA_DIR, './data/db.sqlite');
const db = new Database(dbPath);
const migrationsFolder = path.join(DATA_DIR, 'drizzle');
db.exec(`
CREATE TABLE IF NOT EXISTS ran_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
run_on DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
function sanitizeSql(content: string) {
return content
.split(/\r?\n/)
.filter(
(l) => !l.trim().startsWith('-->') && !l.includes('statement-breakpoint'),
)
.join('\n');
}
fs.readdirSync(migrationsFolder)
.filter((f) => f.endsWith('.sql'))
.sort()
.forEach((file) => {
const filePath = path.join(migrationsFolder, file);
let content = fs.readFileSync(filePath, 'utf-8');
content = sanitizeSql(content);
const migrationName = file.split('_')[0] || file;
const already = db
.prepare('SELECT 1 FROM ran_migrations WHERE name = ?')
.get(migrationName);
if (already) {
console.log(`Skipping already-applied migration: ${file}`);
return;
}
try {
if (migrationName === '0001') {
const messages = db
.prepare(
'SELECT id, type, metadata, content, chatId, messageId FROM messages',
)
.all();
db.exec(`
CREATE TABLE IF NOT EXISTS messages_with_sources (
id INTEGER PRIMARY KEY,
type TEXT NOT NULL,
chatId TEXT NOT NULL,
createdAt TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
messageId TEXT NOT NULL,
content TEXT,
sources TEXT DEFAULT '[]'
);
`);
const insertMessage = db.prepare(`
INSERT INTO messages_with_sources (type, chatId, createdAt, messageId, content, sources)
VALUES (?, ?, ?, ?, ?, ?)
`);
messages.forEach((msg: any) => {
while (typeof msg.metadata === 'string') {
msg.metadata = JSON.parse(msg.metadata || '{}');
}
if (msg.type === 'user') {
insertMessage.run(
'user',
msg.chatId,
msg.metadata['createdAt'],
msg.messageId,
msg.content,
'[]',
);
} else if (msg.type === 'assistant') {
insertMessage.run(
'assistant',
msg.chatId,
msg.metadata['createdAt'],
msg.messageId,
msg.content,
'[]',
);
const sources = msg.metadata['sources'] || '[]';
if (sources && sources.length > 0) {
insertMessage.run(
'source',
msg.chatId,
msg.metadata['createdAt'],
`${msg.messageId}-source`,
'',
JSON.stringify(sources),
);
}
}
});
db.exec('DROP TABLE messages;');
db.exec('ALTER TABLE messages_with_sources RENAME TO messages;');
} else {
db.exec(content);
}
db.prepare('INSERT OR IGNORE INTO ran_migrations (name) VALUES (?)').run(
migrationName,
);
console.log(`Applied migration: ${file}`);
} catch (err) {
console.error(`Failed to apply migration ${file}:`, err);
throw err;
}
});