diff --git a/apps/backend/server.js b/apps/backend/server.js index 4c5fb33..a891b19 100644 --- a/apps/backend/server.js +++ b/apps/backend/server.js @@ -955,6 +955,93 @@ const requireAdmin = (req, res, next) => { return res.status(401).json({ error: 'Authentication required' }); }; +// Middleware to check if user has granted a specific consent category +const requireConsent = (category) => { + return async (req, res, next) => { + try { + const userId = req.session?.userId; + const sessionId = req.sessionID; + let consent; + if (userId) { + consent = await userDbGet('SELECT granted FROM user_consents WHERE user_id = ? AND category = ?', [userId, category]); + } else if (sessionId) { + consent = await userDbGet('SELECT granted FROM user_consents WHERE session_id = ? AND category = ?', [sessionId, category]); + } + if (consent && consent.granted) return next(); + return res.status(403).json({ error: `Consent required: ${category}` }); + } catch (error) { + console.error('Consent check error:', error); + res.status(500).json({ error: 'Internal server error' }); + } + }; +}; + +// ========== CONSENT MANAGEMENT ========== +app.get('/api/consents', async (req, res) => { + try { + const userId = req.session?.userId; + const sessionId = req.sessionID; + const categories = ['essential', 'analytics', 'preferences', 'health_data']; + const result = {}; + categories.forEach(c => { result[c] = c === 'essential'; }); + if (userId) { + const rows = await userDbAll('SELECT category, granted FROM user_consents WHERE user_id = ?', [userId]); + rows.forEach(r => { result[r.category] = r.granted; }); + } else if (sessionId) { + const rows = await userDbAll('SELECT category, granted FROM user_consents WHERE session_id = ?', [sessionId]); + rows.forEach(r => { result[r.category] = r.granted; }); + } + result.essential = true; + res.json(result); + } catch (error) { + console.error('Error fetching consents:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + +app.put('/api/consents', async (req, res) => { + try { + const userId = req.session?.userId; + const sessionId = req.sessionID; + const { categories } = req.body; + if (!categories || typeof categories !== 'object') { + return res.status(400).json({ error: 'categories object required' }); + } + const allowedCategories = ['analytics', 'preferences', 'health_data']; + for (const cat of allowedCategories) { + if (cat in categories) { + const granted = Boolean(categories[cat]); + if (userId) { + await userDbRun( + `INSERT INTO user_consents (user_id, category, granted, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (user_id, category) DO UPDATE SET granted = ?, updated_at = CURRENT_TIMESTAMP`, + [userId, cat, granted, granted] + ); + } else if (sessionId) { + await userDbRun( + `INSERT INTO user_consents (session_id, category, granted, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP) ON CONFLICT (session_id, category) DO UPDATE SET granted = ?, updated_at = CURRENT_TIMESTAMP`, + [sessionId, cat, granted, granted] + ); + } + } + } + // Return updated consents + const result = { essential: true }; + const cats = ['analytics', 'preferences', 'health_data']; + if (userId) { + const rows = await userDbAll('SELECT category, granted FROM user_consents WHERE user_id = ?', [userId]); + rows.forEach(r => { result[r.category] = r.granted; }); + } else if (sessionId) { + const rows = await userDbAll('SELECT category, granted FROM user_consents WHERE session_id = ?', [sessionId]); + rows.forEach(r => { result[r.category] = r.granted; }); + } + cats.forEach(c => { if (!(c in result)) result[c] = false; }); + res.json(result); + } catch (error) { + console.error('Error saving consents:', error); + res.status(500).json({ error: 'Internal server error' }); + } +}); + // ========== RECENT SEARCHES (database-based) ========== const MAX_RECENT = 5;