const { Pool } = require('pg'); const jwt = require('jsonwebtoken'); const pool = new Pool({ connectionString: process.env.DATABASE_URL, ssl: process.env.NODE_ENV === 'production' ? { rejectUnauthorized: false } : false, }); // Verify JWT token and extract user info function verifyAuth(req) { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { throw new Error('No authorization token provided'); } const token = authHeader.substring(7); const jwtSecret = process.env.JWT_SECRET || 'fallback-secret-change-in-production'; try { const decoded = jwt.verify(token, jwtSecret); return decoded; } catch (error) { throw new Error('Invalid or expired token'); } } export default async function handler(req, res) { const client = await pool.connect(); try { const user = verifyAuth(req); // Ensure ocr_settings column exists (auto-migration) try { await client.query(` ALTER TABLE user_preferences ADD COLUMN IF NOT EXISTS ocr_settings JSONB DEFAULT '{ "preferred_service": "openai", "openai_api_key": "", "ollama_url": "http://localhost:11434", "auto_add_to_collection": false, "confidence_threshold": 80 }'::jsonb; `); } catch (error) { // Column might already exist, ignore error console.log('OCR settings column may already exist:', error.message); } if (req.method === 'GET') { // Get user preferences const result = await client.query(` SELECT default_view, items_per_page, enable_animations, enable_ocr, theme, privacy_settings, ocr_settings, created_at, updated_at FROM user_preferences WHERE user_id = $1 `, [user.userId]); if (result.rows.length === 0) { // Create default preferences if none exist const defaultPrefs = { default_view: 'card', items_per_page: 20, enable_animations: true, enable_ocr: true, theme: 'light', privacy_settings: { collections_public: false, decks_public: false }, ocr_settings: { preferred_service: 'openai', openai_api_key: '', ollama_url: 'http://localhost:11434', auto_add_to_collection: false, confidence_threshold: 80 } }; await client.query(` INSERT INTO user_preferences ( user_id, default_view, items_per_page, enable_animations, enable_ocr, theme, privacy_settings, ocr_settings ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) `, [ user.userId, defaultPrefs.default_view, defaultPrefs.items_per_page, defaultPrefs.enable_animations, defaultPrefs.enable_ocr, defaultPrefs.theme, JSON.stringify(defaultPrefs.privacy_settings), JSON.stringify(defaultPrefs.ocr_settings) ]); return res.status(200).json({ success: true, preferences: defaultPrefs }); } const preferences = result.rows[0]; res.status(200).json({ success: true, preferences: { defaultView: preferences.default_view, itemsPerPage: preferences.items_per_page, enableAnimations: preferences.enable_animations, enableOcr: preferences.enable_ocr, theme: preferences.theme, privacySettings: preferences.privacy_settings, ocrSettings: preferences.ocr_settings || { preferred_service: 'openai', openai_api_key: '', ollama_url: 'http://localhost:11434', auto_add_to_collection: false, confidence_threshold: 80 }, createdAt: preferences.created_at, updatedAt: preferences.updated_at } }); } else if (req.method === 'PUT') { // Update user preferences const { defaultView, itemsPerPage, enableAnimations, enableOcr, theme, privacySettings, ocrSettings } = req.body; // Validate OCR settings if provided if (ocrSettings) { const allowedServices = ['openai', 'ollama']; if (ocrSettings.preferred_service && !allowedServices.includes(ocrSettings.preferred_service)) { return res.status(400).json({ error: 'Invalid OCR service. Must be "openai" or "ollama"' }); } if (ocrSettings.confidence_threshold && (ocrSettings.confidence_threshold < 0 || ocrSettings.confidence_threshold > 100)) { return res.status(400).json({ error: 'Confidence threshold must be between 0 and 100' }); } } // Update preferences (upsert) const result = await client.query(` INSERT INTO user_preferences ( user_id, default_view, items_per_page, enable_animations, enable_ocr, theme, privacy_settings, ocr_settings, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, CURRENT_TIMESTAMP) ON CONFLICT (user_id) DO UPDATE SET default_view = COALESCE($2, user_preferences.default_view), items_per_page = COALESCE($3, user_preferences.items_per_page), enable_animations = COALESCE($4, user_preferences.enable_animations), enable_ocr = COALESCE($5, user_preferences.enable_ocr), theme = COALESCE($6, user_preferences.theme), privacy_settings = COALESCE($7, user_preferences.privacy_settings), ocr_settings = COALESCE($8, user_preferences.ocr_settings), updated_at = CURRENT_TIMESTAMP RETURNING * `, [ user.userId, defaultView, itemsPerPage, enableAnimations, enableOcr, theme, privacySettings ? JSON.stringify(privacySettings) : null, ocrSettings ? JSON.stringify(ocrSettings) : null ]); const preferences = result.rows[0]; res.status(200).json({ success: true, message: 'Preferences updated successfully', preferences: { defaultView: preferences.default_view, itemsPerPage: preferences.items_per_page, enableAnimations: preferences.enable_animations, enableOcr: preferences.enable_ocr, theme: preferences.theme, privacySettings: preferences.privacy_settings, ocrSettings: preferences.ocr_settings, updatedAt: preferences.updated_at } }); } else { res.status(405).json({ error: 'Method not allowed' }); } } catch (error) { console.error('User preferences error:', error); if (error.message.includes('authorization') || error.message.includes('token')) { res.status(401).json({ error: 'Unauthorized' }); } else { res.status(500).json({ error: 'Failed to manage preferences', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } } finally { client.release(); } }