const Feedback = require('../models/Feedback'); const Ride = require('../models/Ride'); const RideRequest = require('../models/RideRequest'); const { User } = require('../models/user'); /** * @desc Crea un feedback per un viaggio * @route POST /api/trasporti/feedback * @access Private */ const createFeedback = async (req, res) => { try { const { idapp, rideId, rideRequestId, toUserId, role, rating, categories, comment, pros, cons, tags, isPublic } = req.body; const fromUserId = req.user._id; // Validazione base if (!idapp || !rideId || !toUserId || !role || !rating) { return res.status(400).json({ success: false, message: 'Campi obbligatori: idapp, rideId, toUserId, role, rating' }); } // Verifica rating valido if (rating < 1 || rating > 5) { return res.status(400).json({ success: false, message: 'Il rating deve essere tra 1 e 5' }); } // Verifica che il ride esista e sia completato const ride = await Ride.findById(rideId); if (!ride) { return res.status(404).json({ success: false, message: 'Viaggio non trovato' }); } // Verifica che l'utente abbia partecipato al viaggio const wasDriver = ride.userId.toString() === fromUserId.toString(); const wasPassenger = ride.confirmedPassengers.some( p => p.userId.toString() === fromUserId.toString() ); if (!wasDriver && !wasPassenger) { return res.status(403).json({ success: false, message: 'Non hai partecipato a questo viaggio' }); } // Verifica che non stia valutando se stesso if (fromUserId.toString() === toUserId.toString()) { return res.status(400).json({ success: false, message: 'Non puoi valutare te stesso' }); } // Verifica che non esista già un feedback const existingFeedback = await Feedback.findOne({ rideId, fromUserId, toUserId }); if (existingFeedback) { return res.status(400).json({ success: false, message: 'Hai già lasciato un feedback per questo utente in questo viaggio' }); } // Crea il feedback const feedbackData = { idapp, rideId, fromUserId, toUserId, role, rating }; if (rideRequestId) feedbackData.rideRequestId = rideRequestId; if (categories) feedbackData.categories = categories; if (comment) feedbackData.comment = comment; if (pros) feedbackData.pros = pros; if (cons) feedbackData.cons = cons; if (tags) feedbackData.tags = tags; if (isPublic !== undefined) feedbackData.isPublic = isPublic; // Verifica automatica se il viaggio è completato if (ride.status === 'completed') { feedbackData.isVerified = true; } const feedback = new Feedback(feedbackData); await feedback.save(); // Aggiorna la media rating dell'utente destinatario await updateUserRating(idapp, toUserId); // Aggiorna flag nella richiesta se presente if (rideRequestId) { await RideRequest.findByIdAndUpdate(rideRequestId, { feedbackGiven: true }); } await feedback.populate('fromUserId', 'username name surname profile.img'); await feedback.populate('toUserId', 'username name surname'); res.status(201).json({ success: true, message: 'Feedback inviato con successo!', data: feedback }); } catch (error) { console.error('Errore creazione feedback:', error); res.status(500).json({ success: false, message: 'Errore nella creazione del feedback', error: error.message }); } }; /** * @desc Ottieni i feedback ricevuti da un utente * @route GET /api/trasporti/feedback/user/:userId * @access Public */ const getUserFeedback = async (req, res) => { try { const { userId } = req.params; const { idapp, role, page = 1, limit = 10 } = req.query; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } const query = { idapp, toUserId: userId, isPublic: true }; if (role) { query.role = role; } const skip = (parseInt(page) - 1) * parseInt(limit); const [feedbacks, total, stats] = await Promise.all([ Feedback.find(query) .populate('fromUserId', 'username name surname profile.img') .populate('rideId', 'departure destination dateTime') .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), Feedback.countDocuments(query), getStatsForUser(idapp, userId) ]); res.json({ success: true, data: feedbacks, stats, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / parseInt(limit)) } }); } catch (error) { console.error('Errore recupero feedbacks:', error); res.status(500).json({ success: false, message: 'Errore nel recupero dei feedback', error: error.message }); } }; /** * @desc Ottieni statistiche feedback per un utente * @route GET /api/trasporti/feedback/user/:userId/stats * @access Public */ const getUserFeedbackStats = async (req, res) => { try { const { userId } = req.params; const { idapp } = req.query; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } const [stats, distribution] = await Promise.all([ getStatsForUser(idapp, userId), getRatingDistribution(idapp, userId) ]); res.json({ success: true, data: { ...stats, distribution } }); } catch (error) { console.error('Errore recupero stats:', error); res.status(500).json({ success: false, message: 'Errore nel recupero delle statistiche', error: error.message }); } }; /** * @desc Ottieni i feedback per un viaggio * @route GET /api/trasporti/feedback/ride/:rideId * @access Public (con info limitate) / Private (info complete) */ const getRideFeedback = async (req, res) => { try { const { rideId } = req.params; const { idapp } = req.query; const userId = req.user?._id; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } // Verifica che il ride esista const ride = await Ride.findById(rideId); if (!ride) { return res.status(404).json({ success: false, message: 'Viaggio non trovato' }); } // Query base per feedback pubblici const query = { idapp, rideId, isPublic: true }; const feedbacks = await Feedback.find(query) .populate('fromUserId', 'username name surname profile.img') .populate('toUserId', 'username name surname profile.img') .sort({ createdAt: -1 }); // Se l'utente è autenticato e ha partecipato, mostra info aggiuntive let pendingFeedbacks = []; let myFeedbacks = []; if (userId) { const wasDriver = ride.userId.toString() === userId.toString(); const wasPassenger = ride.confirmedPassengers.some( p => p.userId.toString() === userId.toString() ); if (wasDriver || wasPassenger) { myFeedbacks = feedbacks.filter( f => f.fromUserId._id.toString() === userId.toString() ); if (wasDriver) { // Il conducente deve dare feedback ai passeggeri const feedbackGivenTo = myFeedbacks.map(f => f.toUserId._id.toString()); pendingFeedbacks = ride.confirmedPassengers .filter(p => !feedbackGivenTo.includes(p.userId.toString())) .map(p => ({ userId: p.userId, role: 'passenger' })); } else { // Il passeggero deve dare feedback al conducente const hasGivenToDriver = myFeedbacks.some( f => f.toUserId._id.toString() === ride.userId.toString() ); if (!hasGivenToDriver) { pendingFeedbacks.push({ userId: ride.userId, role: 'driver' }); } } } } res.json({ success: true, data: { feedbacks, pendingFeedbacks, myFeedbacks } }); } catch (error) { console.error('Errore recupero feedbacks viaggio:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; /** * @desc Verifica se l'utente può lasciare un feedback * @route GET /api/trasporti/feedback/can-leave/:rideId/:toUserId * @access Private * @note NUOVA FUNZIONE - Era mancante! */ const canLeaveFeedback = async (req, res) => { try { const { rideId, toUserId } = req.params; const { idapp } = req.query; const fromUserId = req.user._id; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } // Verifica che il ride esista const ride = await Ride.findById(rideId); if (!ride) { return res.status(404).json({ success: false, message: 'Viaggio non trovato' }); } // Verifica che l'utente abbia partecipato al viaggio const wasDriver = ride.userId.toString() === fromUserId.toString(); const wasPassenger = ride.confirmedPassengers.some( p => p.userId.toString() === fromUserId.toString() ); if (!wasDriver && !wasPassenger) { return res.json({ success: true, data: { canLeave: false, reason: 'Non hai partecipato a questo viaggio' } }); } // Verifica che non stia valutando se stesso if (fromUserId.toString() === toUserId.toString()) { return res.json({ success: true, data: { canLeave: false, reason: 'Non puoi valutare te stesso' } }); } // Verifica che toUserId abbia partecipato al viaggio const toUserWasDriver = ride.userId.toString() === toUserId.toString(); const toUserWasPassenger = ride.confirmedPassengers.some( p => p.userId.toString() === toUserId.toString() ); if (!toUserWasDriver && !toUserWasPassenger) { return res.json({ success: true, data: { canLeave: false, reason: 'L\'utente destinatario non ha partecipato a questo viaggio' } }); } // Verifica che il viaggio sia completato if (ride.status !== 'completed') { return res.json({ success: true, data: { canLeave: false, reason: 'Il viaggio non è ancora stato completato', rideStatus: ride.status } }); } // Verifica che non esista già un feedback const existingFeedback = await Feedback.findOne({ rideId, fromUserId, toUserId }); if (existingFeedback) { return res.json({ success: true, data: { canLeave: false, reason: 'Hai già lasciato un feedback per questo utente in questo viaggio', existingFeedbackId: existingFeedback._id } }); } // Determina il ruolo del destinatario const toUserRole = toUserWasDriver ? 'driver' : 'passenger'; // Recupera info utente destinatario const toUser = await User.findById(toUserId) .select('username name surname profile.img'); res.json({ success: true, data: { canLeave: true, toUser: { _id: toUser._id, username: toUser.username, name: toUser.name, surname: toUser.surname, img: toUser.profile?.img }, toUserRole, ride: { _id: ride._id, departure: ride.departure, destination: ride.destination, dateTime: ride.dateTime } } }); } catch (error) { console.error('Errore verifica canLeaveFeedback:', error); res.status(500).json({ success: false, message: 'Errore nella verifica', error: error.message }); } }; /** * @desc Rispondi a un feedback ricevuto * @route POST /api/trasporti/feedback/:id/response * @access Private */ const respondToFeedback = async (req, res) => { try { const { id } = req.params; const { text } = req.body; const userId = req.user._id; if (!text) { return res.status(400).json({ success: false, message: 'Il testo della risposta è obbligatorio' }); } const feedback = await Feedback.findById(id); if (!feedback) { return res.status(404).json({ success: false, message: 'Feedback non trovato' }); } // Solo chi ha ricevuto il feedback può rispondere if (feedback.toUserId.toString() !== userId.toString()) { return res.status(403).json({ success: false, message: 'Non sei autorizzato a rispondere a questo feedback' }); } // Verifica che non abbia già risposto if (feedback.response && feedback.response.text) { return res.status(400).json({ success: false, message: 'Hai già risposto a questo feedback' }); } // Aggiungi la risposta feedback.response = { text, createdAt: new Date() }; await feedback.save(); res.json({ success: true, message: 'Risposta aggiunta', data: feedback }); } catch (error) { console.error('Errore risposta feedback:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; /** * @desc Segna un feedback come utile * @route POST /api/trasporti/feedback/:id/helpful * @access Private */ const markAsHelpful = async (req, res) => { try { const { id } = req.params; const userId = req.user._id; const feedback = await Feedback.findById(id); if (!feedback) { return res.status(404).json({ success: false, message: 'Feedback non trovato' }); } // Inizializza helpful se non esiste if (!feedback.helpful) { feedback.helpful = { count: 0, users: [] }; } // Verifica se l'utente ha già segnato come utile const userIdStr = userId.toString(); const alreadyMarked = feedback.helpful.users.some( u => u.toString() === userIdStr ); if (alreadyMarked) { // Rimuovi il voto feedback.helpful.users = feedback.helpful.users.filter( u => u.toString() !== userIdStr ); feedback.helpful.count = Math.max(0, feedback.helpful.count - 1); } else { // Aggiungi il voto feedback.helpful.users.push(userId); feedback.helpful.count += 1; } await feedback.save(); res.json({ success: true, message: alreadyMarked ? 'Voto rimosso' : 'Feedback segnato come utile', data: { helpfulCount: feedback.helpful.count, isHelpful: !alreadyMarked } }); } catch (error) { console.error('Errore mark helpful:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; /** * @desc Segnala un feedback inappropriato * @route POST /api/trasporti/feedback/:id/report * @access Private */ const reportFeedback = async (req, res) => { try { const { id } = req.params; const { reason } = req.body; const userId = req.user._id; if (!reason) { return res.status(400).json({ success: false, message: 'La motivazione è obbligatoria' }); } const feedback = await Feedback.findById(id); if (!feedback) { return res.status(404).json({ success: false, message: 'Feedback non trovato' }); } // Inizializza reports se non esiste if (!feedback.reports) { feedback.reports = []; } // Verifica se l'utente ha già segnalato const alreadyReported = feedback.reports.some( r => r.userId.toString() === userId.toString() ); if (alreadyReported) { return res.status(400).json({ success: false, message: 'Hai già segnalato questo feedback' }); } // Aggiungi la segnalazione feedback.reports.push({ userId, reason, createdAt: new Date() }); // Se ci sono troppe segnalazioni, nascondi automaticamente if (feedback.reports.length >= 3) { feedback.isPublic = false; feedback.hiddenReason = 'Nascosto automaticamente per multiple segnalazioni'; } await feedback.save(); res.json({ success: true, message: 'Feedback segnalato. Lo esamineremo al più presto.' }); } catch (error) { console.error('Errore segnalazione:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; /** * @desc Ottieni i miei feedback dati * @route GET /api/trasporti/feedback/my/given * @access Private */ const getMyGivenFeedback = async (req, res) => { try { const userId = req.user._id; const { idapp, page = 1, limit = 20 } = req.query; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } const skip = (parseInt(page) - 1) * parseInt(limit); const [feedbacks, total] = await Promise.all([ Feedback.find({ idapp, fromUserId: userId }) .populate('toUserId', 'username name surname profile.img') .populate('rideId', 'departure destination dateTime') .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), Feedback.countDocuments({ idapp, fromUserId: userId }) ]); res.json({ success: true, data: feedbacks, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / parseInt(limit)) } }); } catch (error) { console.error('Errore recupero feedback dati:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; /** * @desc Ottieni i miei feedback ricevuti * @route GET /api/trasporti/feedback/my/received * @access Private */ const getMyReceivedFeedback = async (req, res) => { try { const userId = req.user._id; const { idapp, page = 1, limit = 20 } = req.query; if (!idapp) { return res.status(400).json({ success: false, message: 'idapp è obbligatorio' }); } const skip = (parseInt(page) - 1) * parseInt(limit); const [feedbacks, total, stats] = await Promise.all([ Feedback.find({ idapp, toUserId: userId }) .populate('fromUserId', 'username name surname profile.img') .populate('rideId', 'departure destination dateTime') .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), Feedback.countDocuments({ idapp, toUserId: userId }), getStatsForUser(idapp, userId) ]); res.json({ success: true, data: feedbacks, stats, pagination: { page: parseInt(page), limit: parseInt(limit), total, pages: Math.ceil(total / parseInt(limit)) } }); } catch (error) { console.error('Errore recupero feedback ricevuti:', error); res.status(500).json({ success: false, message: 'Errore', error: error.message }); } }; // ============================================================ // 🔧 HELPER FUNCTIONS // ============================================================ /** * Calcola le statistiche feedback per un utente */ async function getStatsForUser(idapp, userId) { try { const result = await Feedback.aggregate([ { $match: { idapp, toUserId: typeof userId === 'string' ? require('mongoose').Types.ObjectId(userId) : userId } }, { $group: { _id: null, averageRating: { $avg: '$rating' }, totalFeedback: { $sum: 1 }, asDriver: { $sum: { $cond: [{ $eq: ['$role', 'driver'] }, 1, 0] } }, asPassenger: { $sum: { $cond: [{ $eq: ['$role', 'passenger'] }, 1, 0] } } } } ]); if (result.length === 0) { return { averageRating: 0, totalFeedback: 0, asDriver: 0, asPassenger: 0 }; } return { averageRating: Math.round(result[0].averageRating * 10) / 10, totalFeedback: result[0].totalFeedback, asDriver: result[0].asDriver, asPassenger: result[0].asPassenger }; } catch (error) { console.error('Errore calcolo stats:', error); return { averageRating: 0, totalFeedback: 0, asDriver: 0, asPassenger: 0 }; } } /** * Calcola la distribuzione dei rating per un utente */ async function getRatingDistribution(idapp, userId) { try { const result = await Feedback.aggregate([ { $match: { idapp, toUserId: typeof userId === 'string' ? require('mongoose').Types.ObjectId(userId) : userId } }, { $group: { _id: '$rating', count: { $sum: 1 } } }, { $sort: { _id: -1 } } ]); // Crea distribuzione completa 1-5 const distribution = {}; for (let i = 1; i <= 5; i++) { distribution[i] = 0; } result.forEach(r => { distribution[r._id] = r.count; }); return distribution; } catch (error) { console.error('Errore calcolo distribuzione:', error); return { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; } } /** * Aggiorna la media rating nel profilo utente */ async function updateUserRating(idapp, userId) { try { const stats = await getStatsForUser(idapp, userId); await User.findByIdAndUpdate(userId, { 'profile.driverProfile.averageRating': stats.averageRating, 'profile.driverProfile.totalFeedback': stats.totalFeedback }); } catch (error) { console.error('Errore aggiornamento rating utente:', error); } } // ============================================================ // 📤 EXPORTS // ============================================================ module.exports = { // Funzioni principali (nomi corretti per le routes) createFeedback, getUserFeedback, // GET /feedback/user/:userId getUserFeedbackStats, // GET /feedback/user/:userId/stats getRideFeedback, // GET /feedback/ride/:rideId canLeaveFeedback, // GET /feedback/can-leave/:rideId/:toUserId ← NUOVA! respondToFeedback, // POST /feedback/:id/response reportFeedback, // POST /feedback/:id/report markAsHelpful, // POST /feedback/:id/helpful getMyGivenFeedback, // GET /feedback/my/given getMyReceivedFeedback, // GET /feedback/my/received // Alias per compatibilità (vecchi nomi) getFeedbacksForUser: getUserFeedback, getFeedbacksForRide: getRideFeedback, getMyGivenFeedbacks: getMyGivenFeedback, getMyReceivedFeedbacks: getMyReceivedFeedback, // Helper functions (esportate per uso in altri moduli) getStatsForUser, getRatingDistribution, updateUserRating };