"use strict"; const sqlite3 = require('sqlite3').verbose(); const path = require("path"); const fs = require("fs"); async function ensureUserDataDB(dbPath) { const dir = path.dirname(dbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE); return new Promise((resolve, reject) => { const schema = ` CREATE TABLE IF NOT EXISTS User ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, profile_picture_url TEXT, email TEXT UNIQUE, password_hash TEXT, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS UserIntegration ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE, platform TEXT NOT NULL DEFAULT 'AniList', access_token TEXT NOT NULL, refresh_token TEXT NOT NULL, token_type TEXT NOT NULL, anilist_user_id INTEGER NOT NULL, expires_at DATETIME NOT NULL, FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS ListEntry ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, entry_id INTEGER NOT NULL, source TEXT NOT NULL, entry_type TEXT NOT NULL, status TEXT NOT NULL, progress INTEGER NOT NULL DEFAULT 0, score INTEGER, start_date DATE, end_date DATE, repeat_count INTEGER NOT NULL DEFAULT 0, notes TEXT, is_private BOOLEAN NOT NULL DEFAULT 0, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE (user_id, entry_id), FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE ); `; db.exec(schema, (err) => { if (err) reject(err); else resolve(true); }); }); } async function ensureAnilistSchema(db) { return new Promise((resolve, reject) => { const schema = ` CREATE TABLE IF NOT EXISTS anime ( id INTEGER PRIMARY KEY, title TEXT, updatedAt INTEGER, cache_created_at INTEGER DEFAULT 0, cache_ttl_ms INTEGER DEFAULT 0, full_data JSON ); CREATE TABLE IF NOT EXISTS trending ( rank INTEGER PRIMARY KEY, id INTEGER, full_data JSON, updated_at INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS top_airing ( rank INTEGER PRIMARY KEY, id INTEGER, full_data JSON, updated_at INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS books ( id INTEGER PRIMARY KEY, title TEXT, updatedAt INTEGER, full_data JSON ); CREATE TABLE IF NOT EXISTS trending_books ( rank INTEGER PRIMARY KEY, id INTEGER, full_data JSON, updated_at INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS popular_books ( rank INTEGER PRIMARY KEY, id INTEGER, full_data JSON, updated_at INTEGER NOT NULL DEFAULT 0 ); `; db.exec(schema, (err) => { if (err) reject(err); else resolve(true); }); }); } async function ensureExtensionsTable(db) { return new Promise((resolve, reject) => { db.exec(` CREATE TABLE IF NOT EXISTS extension ( ext_name TEXT NOT NULL, id TEXT NOT NULL, title TEXT NOT NULL, metadata TEXT NOT NULL, updated_at INTEGER NOT NULL, PRIMARY KEY(ext_name, id) ); `, (err) => { if (err) reject(err); else resolve(true); }); }); } async function ensureCacheTable(db) { return new Promise((resolve, reject) => { db.exec(` CREATE TABLE IF NOT EXISTS cache ( key TEXT PRIMARY KEY, result TEXT NOT NULL, created_at INTEGER NOT NULL, ttl_ms INTEGER NOT NULL ); `, (err) => { if (err) reject(err); else resolve(true); }); }); } function ensureFavoritesDB(dbPath) { const dir = path.dirname(dbPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const exists = fs.existsSync(dbPath); const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE); return new Promise((resolve, reject) => { if (!exists) { const schema = ` CREATE TABLE IF NOT EXISTS favorites ( id TEXT NOT NULL, user_id INTEGER NOT NULL, title TEXT NOT NULL, image_url TEXT NOT NULL, thumbnail_url TEXT NOT NULL DEFAULT "", tags TEXT NOT NULL DEFAULT "", headers TEXT NOT NULL DEFAULT "", provider TEXT NOT NULL DEFAULT "", PRIMARY KEY (id, user_id) ); `; db.exec(schema, (err) => { if (err) reject(err); else resolve(true); }); return; } db.all(`PRAGMA table_info(favorites)`, (err, cols) => { if (err) return reject(err); const hasHeaders = cols.some(c => c.name === "headers"); const hasProvider = cols.some(c => c.name === "provider"); const hasUserId = cols.some(c => c.name === "user_id"); const queries = []; if (!hasHeaders) { queries.push(`ALTER TABLE favorites ADD COLUMN headers TEXT NOT NULL DEFAULT ""`); } if (!hasProvider) { queries.push(`ALTER TABLE favorites ADD COLUMN provider TEXT NOT NULL DEFAULT ""`); } if (!hasUserId) { queries.push(`ALTER TABLE favorites ADD COLUMN user_id INTEGER NOT NULL DEFAULT 1`); } if (queries.length === 0) { return resolve(false); } db.exec(queries.join(";"), (err) => { if (err) reject(err); else resolve(true); }); }); }); } module.exports = { ensureUserDataDB, ensureAnilistSchema, ensureExtensionsTable, ensureCacheTable, ensureFavoritesDB };