const Database = require('better-sqlite3'); const path = require('path'); const fs = require('fs'); const dataDir = path.join(__dirname, '..', '..', 'data'); const dbPath = path.join(dataDir, 'content.db'); // Ensure data directory exists if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir, { recursive: true }); } let db; function getDb() { if (!db) { console.log('[SQLite] Opening database at', dbPath); db = new Database(dbPath); // Optimize performance db.pragma('journal_mode = WAL'); db.pragma('synchronous = NORMAL'); initSchema(); } return db; } function initSchema() { if (!db) throw new Error('Database not initialized'); // Categories (Groups) db.exec(` CREATE TABLE IF NOT EXISTS categories ( id TEXT PRIMARY KEY, -- Composite key: sourceId:categoryId source_id INTEGER NOT NULL, category_id TEXT NOT NULL, type TEXT NOT NULL, -- 'live', 'movie', 'series' name TEXT NOT NULL, parent_id TEXT, -- For nested categories is_hidden INTEGER DEFAULT 0, data JSON -- Extra provider data ); CREATE INDEX IF NOT EXISTS idx_categories_source_type ON categories(source_id, type); `); // Playlist Items (Channels, Movies, Series, Episodes) db.exec(` CREATE TABLE IF NOT EXISTS playlist_items ( id TEXT PRIMARY KEY, -- Composite key: sourceId:itemId source_id INTEGER NOT NULL, item_id TEXT NOT NULL, -- Original ID from provider type TEXT NOT NULL, -- 'live', 'movie', 'series', 'episode' name TEXT NOT NULL, category_id TEXT, -- maps to categories.category_id (not our composite id) parent_id TEXT, -- For episodes -> series_id -- Common Media Fields stream_icon TEXT, stream_url TEXT, -- Direct link if available container_extension TEXT, -- VOD/Series Specific rating REAL, year TEXT, added_at TEXT, -- App State is_hidden INTEGER DEFAULT 0, is_favorite INTEGER DEFAULT 0, data JSON -- Full original JSON object ); CREATE INDEX IF NOT EXISTS idx_items_source_type ON playlist_items(source_id, type); CREATE INDEX IF NOT EXISTS idx_items_category ON playlist_items(source_id, category_id); `); // EPG Programs // Optimized for range queries db.exec(` CREATE TABLE IF NOT EXISTS epg_programs ( id INTEGER PRIMARY KEY AUTOINCREMENT, channel_id TEXT NOT NULL, -- matches playlist_items.id if possible, or mapping key source_id INTEGER NOT NULL, start_time INTEGER NOT NULL, -- Unix timestamp (ms) end_time INTEGER NOT NULL, -- Unix timestamp (ms) title TEXT, description TEXT, data JSON ); CREATE INDEX IF NOT EXISTS idx_epg_channel_time ON epg_programs(channel_id, start_time, end_time); CREATE INDEX IF NOT EXISTS idx_epg_cleanup ON epg_programs(end_time); -- For deleting old programs `); // Sync Status db.exec(` CREATE TABLE IF NOT EXISTS sync_status ( source_id INTEGER NOT NULL, type TEXT NOT NULL, -- 'live', 'vod', 'series', 'epg' last_sync INTEGER NOT NULL, status TEXT, -- 'success', 'error', 'syncing' error TEXT, PRIMARY KEY (source_id, type) ); `); console.log('[SQLite] Schema initialized'); } module.exports = { getDb, initSchema };