112 lines
3.7 KiB
JavaScript
112 lines
3.7 KiB
JavaScript
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
|
|
};
|