diff --git a/backend/src/db.ts b/backend/src/db.ts index 014bebf..399e93e 100644 --- a/backend/src/db.ts +++ b/backend/src/db.ts @@ -1,6 +1,8 @@ import { Database } from 'bun:sqlite'; import { DB_PATH } from './config'; import { logger } from './globals'; +import { readdir, readFile } from "fs/promises"; +import { join } from "path"; export let FTS5Enabled = true; @@ -18,56 +20,45 @@ export const initializeDB = () => { logger.warn("Failed to load FTS5 extension. Disabling FTS5"); FTS5Enabled = false; } - } - - db.run(` - CREATE TABLE IF NOT EXISTS channels ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL, - createdAt DATETIME DEFAULT CURRENT_TIMESTAMP - ) - `); - - db.run(` - CREATE TABLE IF NOT EXISTS files ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - channelId INTEGER, - filePath TEXT, - fileType TEXT, - fileSize INTEGER, - originalName TEXT, - createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE - ) - `); - - db.run(` - CREATE TABLE IF NOT EXISTS messages ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - channelId INTEGER, - content TEXT, - fileId INTEGER NULL, - createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, - FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE SET NULL - ) - `); - - db.run(` - CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( - content, - content='messages', - content_rowid='id' - ); - `) - return FTS5Enabled; } +export const migrate = async () => { + logger.info(`Checking for migrations...`); + const result = db.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='meta'`); + if (result.all().length === 0) { + logger.info(`Creating meta table...`); + db.run(`CREATE TABLE meta (version INTEGER)`); + db.run(`INSERT INTO meta (version) VALUES (0)`); + } + + const version = db.query(`SELECT version FROM meta`).get() as { version: number }; + logger.info(`Migration version: ${version.version}`); + // we are in bun.js. use its API's to read the file list. + logger.info(`Searching for migrations in ${join(__dirname, "migrations")}`); + const files = await readdir(join(__dirname, "migrations")); + + for (const file of files) { + const [fileVersion, ...rest] = file.split("_"); + logger.info(`Found migration ${fileVersion}`); + if (fileVersion && Number(fileVersion) > version.version) { + logger.info(`Running migration ${file}`); + const sql = new TextDecoder().decode(await readFile(join(__dirname, `migrations/${file}`))); + db.run(sql); + const query = db.query(`UPDATE meta SET version = $version`); + const res = query.run( {$version: Number(fileVersion)}) + logger.info(`Migration ${file} done`); + } + } + logger.info(`Migrations done`); +} + logger.info(`Loading database at ${DB_PATH}`); + export const db = new Database(DB_PATH); -initializeDB(); \ No newline at end of file +initializeDB(); +migrate(); \ No newline at end of file diff --git a/backend/src/migrations/0_init.sql b/backend/src/migrations/0_init.sql new file mode 100644 index 0000000..4ed7e39 --- /dev/null +++ b/backend/src/migrations/0_init.sql @@ -0,0 +1,28 @@ +CREATE TABLE IF NOT EXISTS channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP +) CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + filePath TEXT, + fileType TEXT, + fileSize INTEGER, + originalName TEXT, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE +) CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + content TEXT, + fileId INTEGER NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE + SET + NULL +) CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5( + content, + content = 'messages', + content_rowid = 'id' +); \ No newline at end of file diff --git a/backend/src/migrations/1_localtime.sql b/backend/src/migrations/1_localtime.sql new file mode 100644 index 0000000..1b0f8cb --- /dev/null +++ b/backend/src/migrations/1_localtime.sql @@ -0,0 +1,52 @@ +-- 1. Create a backup of the existing tables +CREATE TABLE channels_backup AS SELECT * FROM channels; +CREATE TABLE files_backup AS SELECT * FROM files; +CREATE TABLE messages_backup AS SELECT * FROM messages; + +-- 2. Drop the existing tables +DROP TABLE channels; +DROP TABLE files; +DROP TABLE messages; + +-- 3. Recreate the tables with the updated schema +CREATE TABLE IF NOT EXISTS channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')) +); + +CREATE TABLE IF NOT EXISTS files ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + filePath TEXT, + fileType TEXT, + fileSize INTEGER, + originalName TEXT, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')), + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channelId INTEGER, + content TEXT, + fileId INTEGER NULL, + createdAt DATETIME DEFAULT (datetime('now', 'localtime')), + FOREIGN KEY (channelId) REFERENCES channels (id) ON DELETE CASCADE, + FOREIGN KEY (fileId) REFERENCES files (id) ON DELETE SET NULL +); + +-- 4. Migrate the data back from the backup tables +INSERT INTO channels (id, name, createdAt) +SELECT id, name, createdAt FROM channels_backup; + +INSERT INTO files (id, channelId, filePath, fileType, fileSize, originalName, createdAt) +SELECT id, channelId, filePath, fileType, fileSize, originalName, createdAt FROM files_backup; + +INSERT INTO messages (id, channelId, content, fileId, createdAt) +SELECT id, channelId, content, fileId, createdAt FROM messages_backup; + +-- 5. Drop the backup tables +DROP TABLE channels_backup; +DROP TABLE files_backup; +DROP TABLE messages_backup;