import Database from 'better-sqlite3'; import type { Config } from '../config/index.js'; import path from 'path'; import fs from 'fs'; export function createDatabase(config: Config): Database.Database { const dbPath = path.resolve(process.cwd(), config.database.path); const dbDir = path.dirname(dbPath); // Ensure directory exists if (!fs.existsSync(dbDir)) { fs.mkdirSync(dbDir, { recursive: true }); } const db = new Database(dbPath); // Enable WAL mode for better concurrency db.pragma('journal_mode = WAL'); db.pragma('foreign_keys = ON'); return db; } export function runMigrations(db: Database.Database): void { // Create tables if they don't exist db.exec(` CREATE TABLE IF NOT EXISTS processed_posts ( id TEXT PRIMARY KEY, subreddit TEXT NOT NULL, author TEXT NOT NULL, title TEXT NOT NULL, image_url TEXT, image_analysis TEXT, generated_message TEXT, processed_at INTEGER NOT NULL, message_sent INTEGER DEFAULT 0, error TEXT ); CREATE TABLE IF NOT EXISTS sent_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, post_id TEXT NOT NULL, recipient TEXT NOT NULL, subject TEXT NOT NULL, message TEXT NOT NULL, sent_at INTEGER NOT NULL, FOREIGN KEY (post_id) REFERENCES processed_posts(id) ); CREATE TABLE IF NOT EXISTS successful_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, sent_message_id INTEGER, original_message TEXT NOT NULL, image_analysis TEXT, post_title TEXT, response_snippet TEXT, created_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS activity_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER NOT NULL, type TEXT NOT NULL, message TEXT NOT NULL, recipient TEXT, image_url TEXT ); CREATE INDEX IF NOT EXISTS idx_posts_subreddit ON processed_posts(subreddit); CREATE INDEX IF NOT EXISTS idx_posts_processed_at ON processed_posts(processed_at); CREATE INDEX IF NOT EXISTS idx_posts_message_sent ON processed_posts(message_sent); CREATE INDEX IF NOT EXISTS idx_activity_log_timestamp ON activity_log(timestamp); `); // Add new columns to existing tables (safe to run multiple times) const addColumnIfNotExists = (table: string, column: string, definition: string) => { const columns = db.prepare(`PRAGMA table_info(${table})`).all() as { name: string }[]; const exists = columns.some((c) => c.name === column); if (!exists) { db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`); } }; addColumnIfNotExists('sent_messages', 'got_response', 'INTEGER DEFAULT 0'); addColumnIfNotExists('sent_messages', 'response_checked_at', 'INTEGER'); // Create index for response checking db.exec(`CREATE INDEX IF NOT EXISTS idx_sent_messages_response ON sent_messages(got_response)`); db.exec(`CREATE INDEX IF NOT EXISTS idx_sent_messages_recipient ON sent_messages(recipient)`); // Add strategy_id column for A/B testing addColumnIfNotExists('sent_messages', 'strategy_id', 'TEXT'); // Add subreddit column to successful_messages for smarter example selection addColumnIfNotExists('successful_messages', 'subreddit', 'TEXT'); // Migration: Make sent_message_id nullable in successful_messages // SQLite doesn't support ALTER COLUMN, so we need to recreate the table const successfulMsgColumns = db.prepare(`PRAGMA table_info(successful_messages)`).all() as { name: string; notnull: number }[]; const sentMsgIdCol = successfulMsgColumns.find((c) => c.name === 'sent_message_id'); if (sentMsgIdCol && sentMsgIdCol.notnull === 1) { // Column is currently NOT NULL, we need to migrate db.exec(` -- Create new table with nullable sent_message_id CREATE TABLE successful_messages_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, sent_message_id INTEGER, original_message TEXT NOT NULL, image_analysis TEXT, post_title TEXT, response_snippet TEXT, created_at INTEGER NOT NULL ); -- Copy existing data INSERT INTO successful_messages_new SELECT id, sent_message_id, original_message, image_analysis, post_title, response_snippet, created_at FROM successful_messages; -- Drop old table DROP TABLE successful_messages; -- Rename new table ALTER TABLE successful_messages_new RENAME TO successful_messages; `); } }