diff --git a/server/db/sqlite.js b/server/db/sqlite.js index fd97eb5..a64ebd0 100644 --- a/server/db/sqlite.js +++ b/server/db/sqlite.js @@ -102,10 +102,89 @@ function initSchema() { ); `); + // User Favorites (per-user) + db.exec(` + CREATE TABLE IF NOT EXISTS favorites ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + source_id INTEGER NOT NULL, + item_id TEXT NOT NULL, + item_type TEXT NOT NULL, -- 'channel', 'movie', 'series' + created_at DATETIME DEFAULT CURRENT_TIMESTAMP, + UNIQUE(user_id, source_id, item_id, item_type) + ); + CREATE INDEX IF NOT EXISTS idx_favorites_user ON favorites(user_id); + CREATE INDEX IF NOT EXISTS idx_favorites_user_type ON favorites(user_id, item_type); + `); + console.log('[SQLite] Schema initialized'); } +// ============================================================ +// Favorites CRUD Operations +// ============================================================ +const favorites = { + getAll(userId, sourceId = null, itemType = null) { + const db = getDb(); + let sql = 'SELECT * FROM favorites WHERE user_id = ?'; + const params = [userId]; + + if (sourceId) { + sql += ' AND source_id = ?'; + params.push(sourceId); + } + if (itemType) { + sql += ' AND item_type = ?'; + params.push(itemType); + } + + sql += ' ORDER BY created_at DESC'; + return db.prepare(sql).all(...params); + }, + + add(userId, sourceId, itemId, itemType = 'channel') { + const db = getDb(); + const stmt = db.prepare(` + INSERT OR IGNORE INTO favorites (user_id, source_id, item_id, item_type) + VALUES (?, ?, ?, ?) + `); + const result = stmt.run(userId, sourceId, itemId, itemType); + return result.changes > 0; + }, + + remove(userId, sourceId, itemId, itemType = 'channel') { + const db = getDb(); + const stmt = db.prepare(` + DELETE FROM favorites + WHERE user_id = ? AND source_id = ? AND item_id = ? AND item_type = ? + `); + const result = stmt.run(userId, sourceId, itemId, itemType); + return result.changes > 0; + }, + + isFavorite(userId, sourceId, itemId, itemType = 'channel') { + const db = getDb(); + const row = db.prepare(` + SELECT 1 FROM favorites + WHERE user_id = ? AND source_id = ? AND item_id = ? AND item_type = ? + `).get(userId, sourceId, itemId, itemType); + return !!row; + }, + + // Get all favorites for a user, grouped by type (for bulk checks) + getAllAsSet(userId) { + const db = getDb(); + const rows = db.prepare('SELECT source_id, item_id, item_type FROM favorites WHERE user_id = ?').all(userId); + const set = new Set(); + for (const row of rows) { + set.add(`${row.source_id}:${row.item_id}:${row.item_type}`); + } + return set; + } +}; + module.exports = { getDb, - initSchema + initSchema, + favorites }; diff --git a/server/routes/favorites.js b/server/routes/favorites.js index 8533d68..980e1bb 100644 --- a/server/routes/favorites.js +++ b/server/routes/favorites.js @@ -1,19 +1,23 @@ const express = require('express'); const router = express.Router(); -const { favorites } = require('../db'); +const { favorites } = require('../db/sqlite'); +const { requireAuth } = require('../auth'); -// Get all favorites +// All favorites routes require authentication +router.use(requireAuth); + +// Get all favorites for current user router.get('/', async (req, res) => { try { const { sourceId, itemType } = req.query; - const items = await favorites.getAll(sourceId, itemType); + const items = favorites.getAll(req.user.id, sourceId || null, itemType || null); res.json(items); } catch (err) { res.status(500).json({ error: err.message }); } }); -// Add favorite +// Add favorite for current user router.post('/', async (req, res) => { try { const { sourceId, itemId, itemType = 'channel' } = req.body; @@ -21,14 +25,14 @@ router.post('/', async (req, res) => { return res.status(400).json({ error: 'Source ID and Item ID are required' }); } - await favorites.add(sourceId, itemId, itemType); + favorites.add(req.user.id, sourceId, itemId, itemType); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); -// Remove favorite +// Remove favorite for current user router.delete('/', async (req, res) => { try { const { sourceId, itemId, itemType = 'channel' } = req.body; @@ -36,14 +40,14 @@ router.delete('/', async (req, res) => { return res.status(400).json({ error: 'Source ID and Item ID are required' }); } - await favorites.remove(sourceId, itemId, itemType); + favorites.remove(req.user.id, sourceId, itemId, itemType); res.json({ success: true }); } catch (err) { res.status(500).json({ error: err.message }); } }); -// Check if item is favorited +// Check if item is favorited by current user router.get('/check', async (req, res) => { try { const { sourceId, itemId, itemType = 'channel' } = req.query; @@ -51,7 +55,7 @@ router.get('/check', async (req, res) => { return res.status(400).json({ error: 'Source ID and Item ID are required' }); } - const isFav = await favorites.isFavorite(sourceId, itemId, itemType); + const isFav = favorites.isFavorite(req.user.id, sourceId, itemId, itemType); res.json({ isFavorite: isFav }); } catch (err) { res.status(500).json({ error: err.message }); @@ -59,3 +63,4 @@ router.get('/check', async (req, res) => { }); module.exports = router; +