This commit is contained in:
Surya Paolo
2025-12-24 19:49:26 +01:00
parent cb965eaa27
commit 85141df8a4
4 changed files with 592 additions and 383 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,8 @@
const Ride = require('../models/Ride');
const { User } = require('../models/user');
const RideRequest = require('../models/RideRequest');
const Feedback = require('../models/Feedback');
const Chat = require('../models/Chat');
/**
* @desc Crea un nuovo viaggio (offerta o richiesta)

View File

@@ -1,8 +1,21 @@
const mongoose = require('mongoose');
const RideRequest = require('../models/RideRequest');
const Ride = require('../models/Ride');
const Chat = require('../models/Chat');
const Message = require('../models/Message');
/**
* Helper per convertire ID in ObjectId
*/
const toObjectId = (id) => {
if (!id) return null;
if (id instanceof mongoose.Types.ObjectId) return id;
if (typeof id === 'object' && id._id) {
return new mongoose.Types.ObjectId(id._id.toString());
}
return new mongoose.Types.ObjectId(id.toString());
};
/**
* @desc Crea una richiesta di passaggio
* @route POST /api/viaggi/requests
@@ -11,7 +24,6 @@ const Message = require('../models/Message');
const createRequest = async (req, res) => {
try {
const {
idapp,
rideId,
message,
pickupPoint,
@@ -29,6 +41,7 @@ const createRequest = async (req, res) => {
contribution,
} = req.body;
const idapp = req.body.idapp || req.query.idapp || req.user?.idapp;
const passengerId = req.user._id;
// Validazione
@@ -72,10 +85,10 @@ const createRequest = async (req, res) => {
// Verifica disponibilità posti
const seats = seatsRequested || 1;
if (ride.type === 'offer' && ride.passengers.available < seats) {
if (ride.type === 'offer' && ride.passengers?.available < seats) {
return res.status(400).json({
success: false,
message: `Posti insufficienti. Disponibili: ${ride.passengers.available}`,
message: `Posti insufficienti. Disponibili: ${ride.passengers?.available || 0}`,
});
}
@@ -116,22 +129,40 @@ const createRequest = async (req, res) => {
await rideRequest.populate('rideId', 'departure destination dateTime');
// Crea o recupera la chat tra passeggero e conducente
const chat = await Chat.findOrCreateDirect(idapp, passengerId, ride.userId, rideId);
let chat;
let chatId = null;
try {
chat = await Chat.findOrCreateDirect(idapp, passengerId, ride.userId, rideId);
chatId = chat._id;
// Invia messaggio automatico nella chat
if (message) {
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: passengerId,
text: message,
type: 'ride_request',
metadata: {
rideId,
rideRequestId: rideRequest._id,
},
});
await chatMessage.save();
// Invia messaggio automatico nella chat
if (message) {
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: passengerId,
text: message,
type: 'ride_request',
metadata: {
rideId,
rideRequestId: rideRequest._id,
},
});
await chatMessage.save();
// Aggiorna lastMessage della chat
chat.lastMessage = {
text: message.substring(0, 100),
senderId: passengerId,
timestamp: new Date(),
type: 'ride_request',
};
await chat.save();
}
} catch (chatError) {
console.error('Errore creazione chat:', chatError);
// Non bloccare la creazione della richiesta se la chat fallisce
}
// TODO: Inviare notifica push al conducente
@@ -140,7 +171,7 @@ const createRequest = async (req, res) => {
success: true,
message: 'Richiesta di passaggio inviata!',
data: rideRequest,
chatId: chat._id,
chatId,
});
} catch (error) {
console.error('Errore creazione richiesta:', error);
@@ -160,9 +191,17 @@ const createRequest = async (req, res) => {
const getRequestsForRide = async (req, res) => {
try {
const { rideId } = req.params;
const { idapp, status } = req.query;
const { status } = req.query;
const idapp = req.query.idapp || req.user?.idapp;
const userId = req.user._id;
if (!idapp) {
return res.status(400).json({
success: false,
message: 'idapp è obbligatorio',
});
}
// Verifica che l'utente sia il proprietario del ride
const ride = await Ride.findById(rideId);
if (!ride) {
@@ -213,7 +252,8 @@ const getRequestsForRide = async (req, res) => {
const getMyRequests = async (req, res) => {
try {
const userId = req.user._id;
const { idapp, status, page = 1, limit = 20 } = req.query;
const { status, page = 1, limit = 20 } = req.query;
const idapp = req.query.idapp || req.user?.idapp;
if (!idapp) {
return res.status(400).json({
@@ -272,7 +312,7 @@ const getMyRequests = async (req, res) => {
const getPendingRequests = async (req, res) => {
try {
const userId = req.user._id;
const idapp = req.user.idapp;
const idapp = req.query.idapp || req.user?.idapp;
if (!idapp) {
return res.status(400).json({
@@ -307,15 +347,23 @@ const getPendingRequests = async (req, res) => {
/**
* @desc Accetta una richiesta di passaggio
* @route PUT /api/viaggi/requests/:id/accept
* @route POST /api/viaggi/requests/:id/accept
* @access Private (solo conducente)
*/
const acceptRequest = async (req, res) => {
try {
const { id } = req.params;
const { responseMessage, idapp } = req.body;
const { responseMessage } = req.body;
const idapp = req.body.idapp || req.query.idapp || req.user?.idapp;
const userId = req.user._id;
if (!idapp) {
return res.status(400).json({
success: false,
message: 'idapp è obbligatorio',
});
}
const request = await RideRequest.findById(id).populate('rideId');
if (!request) {
@@ -343,7 +391,14 @@ const acceptRequest = async (req, res) => {
// Verifica disponibilità posti
const ride = request.rideId;
if (ride.passengers.available < request.seatsRequested) {
if (!ride) {
return res.status(404).json({
success: false,
message: 'Viaggio associato non trovato',
});
}
if ((ride.passengers?.available || 0) < request.seatsRequested) {
return res.status(400).json({
success: false,
message: 'Posti non più disponibili',
@@ -357,6 +412,10 @@ const acceptRequest = async (req, res) => {
await request.save();
// Aggiorna il ride con il passeggero
if (!ride.confirmedPassengers) {
ride.confirmedPassengers = [];
}
ride.confirmedPassengers.push({
userId: request.passengerId,
seats: request.seatsRequested,
@@ -364,22 +423,59 @@ const acceptRequest = async (req, res) => {
dropoffPoint: request.dropoffPoint || ride.destination,
confirmedAt: new Date(),
});
await ride.updateAvailableSeats();
// Aggiorna posti disponibili
if (typeof ride.updateAvailableSeats === 'function') {
await ride.updateAvailableSeats();
} else {
// Fallback manuale
const totalConfirmed = ride.confirmedPassengers.reduce(
(sum, p) => sum + (p.seats || 1),
0
);
if (ride.passengers) {
ride.passengers.available = Math.max(0, (ride.passengers.total || 0) - totalConfirmed);
}
await ride.save();
}
// Invia messaggio nella chat
const chat = await Chat.findOrCreateDirect(idapp, userId, request.passengerId, ride._id);
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: userId,
text: responseMessage || '✅ Richiesta accettata! Ci vediamo al punto di partenza.',
type: 'ride_accepted',
metadata: {
rideId: ride._id,
rideRequestId: request._id,
},
});
await chatMessage.save();
try {
const chat = await Chat.findOrCreateDirect(idapp, userId, request.passengerId, ride._id);
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: userId,
text: responseMessage || '✅ Richiesta accettata! Ci vediamo al punto di partenza.',
type: 'ride_accepted',
metadata: {
rideId: ride._id,
rideRequestId: request._id,
},
});
await chatMessage.save();
// Aggiorna lastMessage
chat.lastMessage = {
text: (responseMessage || '✅ Richiesta accettata!').substring(0, 100),
senderId: userId,
timestamp: new Date(),
type: 'ride_accepted',
};
// Incrementa unread per il passeggero
if (!chat.unreadCount) {
chat.unreadCount = new Map();
}
const passengerIdStr = request.passengerId.toString();
const current = chat.unreadCount.get(passengerIdStr) || 0;
chat.unreadCount.set(passengerIdStr, current + 1);
chat.markModified('unreadCount');
await chat.save();
} catch (chatError) {
console.error('Errore invio messaggio chat:', chatError);
}
// TODO: Inviare notifica push al passeggero
@@ -403,15 +499,23 @@ const acceptRequest = async (req, res) => {
/**
* @desc Rifiuta una richiesta di passaggio
* @route PUT /api/viaggi/requests/:id/reject
* @route POST /api/viaggi/requests/:id/reject
* @access Private (solo conducente)
*/
const rejectRequest = async (req, res) => {
try {
const { id } = req.params;
const { responseMessage, idapp } = req.body;
const { responseMessage } = req.body;
const idapp = req.body.idapp || req.query.idapp || req.user?.idapp;
const userId = req.user._id;
if (!idapp) {
return res.status(400).json({
success: false,
message: 'idapp è obbligatorio',
});
}
const request = await RideRequest.findById(id);
if (!request) {
@@ -444,19 +548,41 @@ const rejectRequest = async (req, res) => {
await request.save();
// Invia messaggio nella chat
const chat = await Chat.findOrCreateDirect(idapp, userId, request.passengerId, request.rideId);
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: userId,
text: responseMessage || '❌ Mi dispiace, non posso accettare questa richiesta.',
type: 'ride_rejected',
metadata: {
rideId: request.rideId,
rideRequestId: request._id,
},
});
await chatMessage.save();
try {
const chat = await Chat.findOrCreateDirect(idapp, userId, request.passengerId, request.rideId);
const chatMessage = new Message({
idapp,
chatId: chat._id,
senderId: userId,
text: responseMessage || '❌ Mi dispiace, non posso accettare questa richiesta.',
type: 'ride_rejected',
metadata: {
rideId: request.rideId,
rideRequestId: request._id,
},
});
await chatMessage.save();
// Aggiorna lastMessage e unread
chat.lastMessage = {
text: (responseMessage || '❌ Richiesta rifiutata').substring(0, 100),
senderId: userId,
timestamp: new Date(),
type: 'ride_rejected',
};
if (!chat.unreadCount) {
chat.unreadCount = new Map();
}
const passengerIdStr = request.passengerId.toString();
const current = chat.unreadCount.get(passengerIdStr) || 0;
chat.unreadCount.set(passengerIdStr, current + 1);
chat.markModified('unreadCount');
await chat.save();
} catch (chatError) {
console.error('Errore invio messaggio chat:', chatError);
}
// TODO: Inviare notifica push al passeggero
@@ -476,8 +602,8 @@ const rejectRequest = async (req, res) => {
};
/**
* @desc Cancella una richiesta (dal passeggero)
* @route PUT /api/viaggi/requests/:id/cancel
* @desc Cancella una richiesta (dal passeggero o conducente)
* @route POST /api/viaggi/requests/:id/cancel
* @access Private
*/
const cancelRequest = async (req, res) => {
@@ -517,11 +643,23 @@ const cancelRequest = async (req, res) => {
// Se era accettata, rimuovi il passeggero dal ride
if (request.status === 'accepted') {
const ride = await Ride.findById(request.rideId);
if (ride) {
if (ride && ride.confirmedPassengers) {
ride.confirmedPassengers = ride.confirmedPassengers.filter(
(p) => p.userId.toString() !== request.passengerId.toString()
);
await ride.updateAvailableSeats();
if (typeof ride.updateAvailableSeats === 'function') {
await ride.updateAvailableSeats();
} else {
const totalConfirmed = ride.confirmedPassengers.reduce(
(sum, p) => sum + (p.seats || 1),
0
);
if (ride.passengers) {
ride.passengers.available = Math.max(0, (ride.passengers.total || 0) - totalConfirmed);
}
await ride.save();
}
}
}
@@ -578,8 +716,11 @@ const getRequestById = async (req, res) => {
}
// Verifica che l'utente sia coinvolto
const isPassenger = request.passengerId._id.toString() === userId.toString();
const isDriver = request.driverId._id.toString() === userId.toString();
const passengerId = request.passengerId?._id || request.passengerId;
const driverId = request.driverId?._id || request.driverId;
const isPassenger = passengerId?.toString() === userId.toString();
const isDriver = driverId?.toString() === userId.toString();
if (!isPassenger && !isDriver) {
return res.status(403).json({
@@ -611,9 +752,13 @@ const getReceivedRequests = async (req, res) => {
try {
const driverId = req.user._id;
const { status, page = 1, limit = 20 } = req.query;
const idapp = req.query.idapp || req.user?.idapp;
// Build query
const query = { driverId };
if (idapp) {
query.idapp = idapp;
}
if (status) {
query.status = status;
}
@@ -622,22 +767,28 @@ const getReceivedRequests = async (req, res) => {
// Fetch requests
const requests = await RideRequest.find(query)
.populate('passengerId', 'name surname profile.img') // <-- FIX: passengerId invece di userId
.populate('rideId', 'departure destination departureDate availableSeats')
.populate('passengerId', 'username name surname profile.img profile.driverProfile.averageRating')
.populate('rideId', 'departure destination dateTime passengers')
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit))
.lean();
// Get counts by status
const driverObjectId = toObjectId(driverId);
const matchQuery = { driverId: driverObjectId };
if (idapp) {
matchQuery.idapp = idapp;
}
const counts = await RideRequest.aggregate([
{ $match: { driverId } },
{ $match: matchQuery },
{
$group: {
_id: '$status',
count: { $sum: 1 }
}
}
count: { $sum: 1 },
},
},
]);
// Format counts
@@ -647,11 +798,11 @@ const getReceivedRequests = async (req, res) => {
rejected: 0,
cancelled: 0,
expired: 0,
completed: 0
completed: 0,
};
counts.forEach(c => {
if (statusCounts.hasOwnProperty(c._id)) {
counts.forEach((c) => {
if (Object.prototype.hasOwnProperty.call(statusCounts, c._id)) {
statusCounts[c._id] = c.count;
}
});
@@ -668,20 +819,20 @@ const getReceivedRequests = async (req, res) => {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / parseInt(limit))
}
}
pages: Math.ceil(total / parseInt(limit)),
},
},
});
} catch (error) {
console.error('Error fetching received requests:', error);
res.status(500).json({
success: false,
message: 'Errore nel recupero delle richieste ricevute',
error: error.message
error: error.message,
});
}
};
/**
* @desc Ottieni richieste inviate (io come passeggero)
* @route GET /api/viaggi/requests/sent
@@ -691,9 +842,13 @@ const getSentRequests = async (req, res) => {
try {
const userId = req.user._id;
const { status, page = 1, limit = 20 } = req.query;
const idapp = req.query.idapp || req.user?.idapp;
// Build query - FIX: usa passengerId invece di userId
// Build query
const query = { passengerId: userId };
if (idapp) {
query.idapp = idapp;
}
if (status) {
query.status = status;
}
@@ -702,29 +857,59 @@ const getSentRequests = async (req, res) => {
// Fetch requests
const requests = await RideRequest.find(query)
.populate('driverId', 'name surname profile.img')
.populate('rideId', 'departure destination departureDate availableSeats currentPassengers')
.populate('driverId', 'username name surname profile.img profile.driverProfile.averageRating')
.populate('rideId', 'departure destination dateTime passengers')
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit))
.lean();
// Enrich with ride info
const enrichedRequests = requests.map(request => {
const req = { ...request };
// Add ride info if rideId is populated
if (req.rideId) {
req.rideInfo = {
departure: req.rideId.departure?.city || req.rideId.departure,
destination: req.rideId.destination?.city || req.rideId.destination,
departureDate: req.rideId.departureDate,
availableSeats: req.rideId.availableSeats,
currentPassengers: req.rideId.currentPassengers || 0
const enrichedRequests = requests.map((request) => {
const enriched = { ...request };
if (enriched.rideId) {
enriched.rideInfo = {
departure: enriched.rideId.departure?.city || enriched.rideId.departure,
destination: enriched.rideId.destination?.city || enriched.rideId.destination,
dateTime: enriched.rideId.dateTime,
availableSeats: enriched.rideId.passengers?.available || 0,
};
}
return req;
return enriched;
});
// Get counts by status
const passengerObjectId = toObjectId(userId);
const matchQuery = { passengerId: passengerObjectId };
if (idapp) {
matchQuery.idapp = idapp;
}
const counts = await RideRequest.aggregate([
{ $match: matchQuery },
{
$group: {
_id: '$status',
count: { $sum: 1 },
},
},
]);
const statusCounts = {
pending: 0,
accepted: 0,
rejected: 0,
cancelled: 0,
expired: 0,
completed: 0,
};
counts.forEach((c) => {
if (Object.prototype.hasOwnProperty.call(statusCounts, c._id)) {
statusCounts[c._id] = c.count;
}
});
// Total count
@@ -734,21 +919,21 @@ const getSentRequests = async (req, res) => {
success: true,
data: {
requests: enrichedRequests,
counts: statusCounts,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / parseInt(limit))
}
}
pages: Math.ceil(total / parseInt(limit)),
},
},
});
} catch (error) {
console.error('Error fetching sent requests:', error);
res.status(500).json({
success: false,
message: 'Errore nel recupero delle richieste inviate',
error: error.message
error: error.message,
});
}
};
@@ -764,4 +949,4 @@ module.exports = {
getRequestById,
getReceivedRequests,
getSentRequests,
};
};

View File

@@ -142,6 +142,8 @@ router.get('/cities/recent', authenticate, rideController.getRecentCities);
*/
router.post('/requests', authenticate, rideRequestController.createRequest);
// ⚠️ IMPORTANTE: Route specifiche PRIMA di quelle con :id
/**
* @route GET /api/viaggi/requests/received
* @desc Richieste ricevute (sono conducente)
@@ -156,6 +158,20 @@ router.get('/requests/received', authenticate, rideRequestController.getReceived
*/
router.get('/requests/sent', authenticate, rideRequestController.getSentRequests);
/**
* @route GET /api/viaggi/requests/pending
* @desc Richieste pendenti (per il conducente)
* @access Private
*/
router.get('/requests/pending', authenticate, rideRequestController.getPendingRequests);
/**
* @route GET /api/viaggi/requests/my
* @desc Le mie richieste (come passeggero)
* @access Private
*/
router.get('/requests/my', authenticate, rideRequestController.getMyRequests);
/**
* @route GET /api/viaggi/requests/ride/:rideId
* @desc Richieste per un viaggio specifico
@@ -163,6 +179,8 @@ router.get('/requests/sent', authenticate, rideRequestController.getSentRequests
*/
router.get('/requests/ride/:rideId', authenticate, rideRequestController.getRequestsForRide);
// ⚠️ Route con :id DOPO tutte le route specifiche
/**
* @route GET /api/viaggi/requests/:id
* @desc Dettaglio singola richiesta
@@ -190,7 +208,6 @@ router.post('/requests/:id/reject', authenticate, rideRequestController.rejectRe
* @access Private (driver o passenger)
*/
router.post('/requests/:id/cancel', authenticate, rideRequestController.cancelRequest);
// ============================================================
// 💬 CHAT - Messaggistica
// ============================================================
@@ -414,7 +431,7 @@ router.post('/feedback/:id/helpful', authenticate, feedbackController.markAsHelp
router.get('/driver/user/:userId', async (req, res) => {
try {
const { userId } = req.params;
const idapp = req.user.idapp;
const idapp = req.query.idapp;
const { User } = require('../models/user');
const Ride = require('../models/Ride');