Spaces:
Paused
Paused
Download src/controllers/feedback.controller.js from imkrish/yatra-x-feedback: direct link, hf CLI and curl.
- Browser
- Download file 4.65 kB
-
https://huggingface.co/spaces/imkrish/yatra-x-feedback/resolve/main/src/controllers/feedback.controller.js
- Command line
-
hf download hf://spaces/imkrish/yatra-x-feedback/src/controllers/feedback.controller.js
-
curl -L -o feedback.controller.js https://huggingface.co/spaces/imkrish/yatra-x-feedback/resolve/main/src/controllers/feedback.controller.js
4.65 kB
| ; | |
| 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 | |
| }; | |