'use strict'; const { v4: uuidv4 } = require('uuid'); const { getPool } = require('../db'); /** * Create a new feedback event (PostgreSQL) */ async function createFeedbackEvent(req, res) { const { event_type, session_id, user_hash, context = {}, itinerary_snapshot = [], modifications = [], reward = 0 } = req.body; // Validation if (!event_type) { return res.status(400).json({ error: { message: 'event_type is required' } }); } const validEventTypes = [ 'plan_saved', 'plan_completed', 'rating_positive', 'poi_added', 'plan_abandoned', 'poi_removed', 'rating_negative', 'poi_reordered', 'poi_swapped', 'session_start' ]; if (!validEventTypes.includes(event_type)) { return res.status(400).json({ error: { message: `Invalid event_type. Must be one of: ${validEventTypes.join(', ')}` } }); } try { const pool = getPool(); if (!pool) { return res.status(503).json({ error: { message: 'Database not available' } }); } const id = uuidv4(); const query = ` INSERT INTO feedback_events (id, event_type, session_id, user_hash, context, itinerary_snapshot, modifications, reward, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW()) `; const params = [ id, event_type, session_id || null, user_hash || null, JSON.stringify(context), JSON.stringify(itinerary_snapshot), JSON.stringify(modifications), reward ]; await pool.query(query, params); res.status(201).json({ data: { id, event_type, session_id, reward }, message: 'Feedback event recorded successfully' }); } catch (err) { console.error('Error creating feedback event:', err); res.status(500).json({ error: { message: 'Failed to record feedback event' } }); } } /** * List feedback events with optional filters (PostgreSQL) */ async function listFeedbackEvents(req, res) { const { event_type, session_id, limit = 100, offset = 0 } = req.query; try { const pool = getPool(); if (!pool) { return res.status(503).json({ error: { message: 'Database not available' } }); } let query = 'SELECT * FROM feedback_events WHERE 1=1'; const params = []; let paramCounter = 1; if (event_type) { query += ` AND event_type = $${paramCounter++}`; params.push(event_type); } if (session_id) { query += ` AND session_id = $${paramCounter++}`; params.push(session_id); } query += ` ORDER BY created_at DESC LIMIT $${paramCounter++} OFFSET $${paramCounter++}`; params.push(Math.min(parseInt(limit), 1000), parseInt(offset)); const result = await pool.query(query, params); res.json({ data: result.rows }); } catch (err) { console.error('Error listing feedback events:', err); res.status(500).json({ error: { message: 'Failed to list feedback events' } }); } } /** * Get aggregated statistics for a POI (PostgreSQL) */ async function getPOIStats(req, res) { const { poi_id } = req.params; if (!poi_id) { return res.status(400).json({ error: { message: 'poi_id is required' } }); } try { const pool = getPool(); if (!pool) { return res.status(503).json({ error: { message: 'Database not available' } }); } // PostgreSQL JSONB search or simple LIKE on TEXT const query = ` SELECT event_type, reward, modifications FROM feedback_events WHERE itinerary_snapshot::text LIKE $1 `; const result = await pool.query(query, [`%${poi_id}%`]); const events = result.rows; const stats = { poi_id, times_suggested: events.length, times_saved: events.filter(e => e.event_type === 'plan_saved').length, times_removed: events.filter(e => { if (e.event_type !== 'poi_removed') return false; const mods = typeof e.modifications === 'string' ? JSON.parse(e.modifications) : e.modifications; return Array.isArray(mods) && mods.some(m => m.poi_id === poi_id); }).length, avg_reward: events.length > 0 ? (events.reduce((sum, e) => sum + parseFloat(e.reward), 0) / events.length).toFixed(2) : 0, save_rate: 0 }; if (stats.times_suggested > 0) { stats.save_rate = (stats.times_saved / stats.times_suggested).toFixed(2); } res.json({ data: stats }); } catch (err) { console.error('Error getting POI stats:', err); res.status(500).json({ error: { message: 'Failed to get POI stats' } }); } } module.exports = { createFeedbackEvent, listFeedbackEvents, getPOIStats };