import sqlite3 from 'sqlite3'; import { promisify } from 'util'; import path from 'path'; import fs from 'fs'; // Enable verbose mode for debugging const Database = sqlite3.verbose().Database; class DatabaseConnection { private db: sqlite3.Database | null = null; async connect(): Promise { if (this.db) { return this.db; } const dbPath = process.env.DATABASE_URL || path.join(__dirname, '../../database.sqlite'); // Ensure directory exists const dbDir = path.dirname(dbPath); if (!fs.existsSync(dbDir)) { fs.mkdirSync(dbDir, { recursive: true }); } return new Promise((resolve, reject) => { this.db = new Database(dbPath, (err) => { if (err) { console.error('Error opening database:', err.message); reject(err); } else { console.log('Connected to SQLite database'); resolve(this.db!); } }); }); } async query(sql: string, params: any[] = []): Promise { const db = await this.connect(); return new Promise((resolve, reject) => { db.all(sql, params, (err, rows) => { if (err) reject(err); else resolve(rows || []); }); }); } async run(sql: string, params: any[] = []): Promise<{ lastID: number; changes: number }> { const db = await this.connect(); return new Promise((resolve, reject) => { db.run(sql, params, function(err) { if (err) { reject(err); } else { resolve({ lastID: this.lastID || 0, changes: this.changes || 0 }); } }); }); } async get(sql: string, params: any[] = []): Promise { const db = await this.connect(); return new Promise((resolve, reject) => { db.get(sql, params, (err, row) => { if (err) reject(err); else resolve(row); }); }); } async close(): Promise { if (this.db) { return new Promise((resolve, reject) => { this.db!.close((err) => { if (err) { reject(err); } else { this.db = null; resolve(); } }); }); } } } // Singleton instance export const database = new DatabaseConnection(); // Database initialization function export async function initializeDatabase(): Promise { const createTables = [ // Users table `CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT UNIQUE NOT NULL, name TEXT NOT NULL, password_hash TEXT, avatar TEXT, google_id TEXT UNIQUE, email_verified BOOLEAN DEFAULT FALSE, verification_token TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, // Tenants table `CREATE TABLE IF NOT EXISTS tenants ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, subdomain TEXT UNIQUE, plan TEXT DEFAULT 'starter', settings TEXT DEFAULT '{}', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP )`, // User-Tenant relationships `CREATE TABLE IF NOT EXISTS user_tenants ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, tenant_id INTEGER NOT NULL, role TEXT DEFAULT 'owner', permissions TEXT DEFAULT '{}', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE, UNIQUE(user_id, tenant_id) )`, // Domains table `CREATE TABLE IF NOT EXISTS domains ( id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id INTEGER NOT NULL, domain TEXT NOT NULL, verified BOOLEAN DEFAULT FALSE, verification_token TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE )`, // Knowledge base table `CREATE TABLE IF NOT EXISTS knowledge_base ( id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id INTEGER NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL, source TEXT NOT NULL, status TEXT DEFAULT 'processing', size INTEGER, metadata TEXT DEFAULT '{}', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE )`, // Chat sessions table `CREATE TABLE IF NOT EXISTS chat_sessions ( id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id INTEGER NOT NULL, domain TEXT, user_ip TEXT, user_agent TEXT, session_token TEXT UNIQUE NOT NULL, started_at DATETIME DEFAULT CURRENT_TIMESTAMP, ended_at DATETIME, resolved BOOLEAN DEFAULT FALSE, rating INTEGER, feedback TEXT, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE )`, // Chat messages table `CREATE TABLE IF NOT EXISTS chat_messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id INTEGER NOT NULL, sender TEXT NOT NULL, -- 'user' or 'ai' message TEXT NOT NULL, metadata TEXT DEFAULT '{}', timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES chat_sessions (id) ON DELETE CASCADE )`, // Analytics events table `CREATE TABLE IF NOT EXISTS analytics_events ( id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id INTEGER NOT NULL, event_type TEXT NOT NULL, event_data TEXT DEFAULT '{}', timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE )`, // Widget configurations table `CREATE TABLE IF NOT EXISTS widget_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, tenant_id INTEGER NOT NULL, config TEXT NOT NULL DEFAULT '{}', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (tenant_id) REFERENCES tenants (id) ON DELETE CASCADE )` ]; const createIndexes = [ `CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)`, `CREATE INDEX IF NOT EXISTS idx_users_google_id ON users(google_id)`, `CREATE INDEX IF NOT EXISTS idx_tenants_subdomain ON tenants(subdomain)`, `CREATE INDEX IF NOT EXISTS idx_user_tenants_user_id ON user_tenants(user_id)`, `CREATE INDEX IF NOT EXISTS idx_user_tenants_tenant_id ON user_tenants(tenant_id)`, `CREATE INDEX IF NOT EXISTS idx_domains_tenant_id ON domains(tenant_id)`, `CREATE INDEX IF NOT EXISTS idx_knowledge_base_tenant_id ON knowledge_base(tenant_id)`, `CREATE INDEX IF NOT EXISTS idx_chat_sessions_tenant_id ON chat_sessions(tenant_id)`, `CREATE INDEX IF NOT EXISTS idx_chat_sessions_token ON chat_sessions(session_token)`, `CREATE INDEX IF NOT EXISTS idx_chat_messages_session_id ON chat_messages(session_id)`, `CREATE INDEX IF NOT EXISTS idx_analytics_events_tenant_id ON analytics_events(tenant_id)`, `CREATE INDEX IF NOT EXISTS idx_analytics_events_timestamp ON analytics_events(timestamp)` ]; try { // Create tables for (const sql of createTables) { await database.run(sql); } // Create indexes for (const sql of createIndexes) { await database.run(sql); } console.log('Database tables and indexes created successfully'); } catch (error) { console.error('Error initializing database:', error); throw error; } }