- Trasporti- Passo 2
This commit is contained in:
@@ -4,7 +4,7 @@ const sharp = require('sharp');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || './upload';
|
||||||
|
|
||||||
const assetController = {
|
const assetController = {
|
||||||
// POST /assets/upload
|
// POST /assets/upload
|
||||||
@@ -50,9 +50,9 @@ const assetController = {
|
|||||||
sourceType: 'upload',
|
sourceType: 'upload',
|
||||||
file: {
|
file: {
|
||||||
path: file.path,
|
path: file.path,
|
||||||
url: `/uploads/${file.filename}`,
|
url: `/upload/${file.filename}`,
|
||||||
thumbnailPath: thumbPath,
|
thumbnailPath: thumbPath,
|
||||||
thumbnailUrl: `/uploads/thumbs/${thumbName}`,
|
thumbnailUrl: `/upload/thumbs/${thumbName}`,
|
||||||
originalName: file.originalname,
|
originalName: file.originalname,
|
||||||
mimeType: file.mimetype,
|
mimeType: file.mimetype,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
@@ -106,7 +106,7 @@ const assetController = {
|
|||||||
sourceType: 'upload',
|
sourceType: 'upload',
|
||||||
file: {
|
file: {
|
||||||
path: file.path,
|
path: file.path,
|
||||||
url: `/uploads/${file.filename}`,
|
url: `/upload/${file.filename}`,
|
||||||
originalName: file.originalname,
|
originalName: file.originalname,
|
||||||
mimeType: file.mimetype,
|
mimeType: file.mimetype,
|
||||||
size: file.size,
|
size: file.size,
|
||||||
@@ -199,7 +199,7 @@ const assetController = {
|
|||||||
sourceType: 'ai',
|
sourceType: 'ai',
|
||||||
file: {
|
file: {
|
||||||
path: filePath,
|
path: filePath,
|
||||||
url: `/uploads/ai-generated/${fileName}`,
|
url: `/upload/ai-generated/${fileName}`,
|
||||||
mimeType: 'image/jpeg',
|
mimeType: 'image/jpeg',
|
||||||
size: fileSize,
|
size: fileSize,
|
||||||
dimensions
|
dimensions
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const Chat = require('../models/Chat');
|
const Chat = require('../models/Chat');
|
||||||
const Message = require('../models/Message');
|
const Message = require('../models/Message');
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Ottieni tutte le chat dell'utente
|
* @desc Ottieni tutte le chat dell'utente
|
||||||
@@ -10,7 +10,8 @@ const { User } = require('../models/User');
|
|||||||
const getMyChats = async (req, res) => {
|
const getMyChats = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { idapp, page = 1, limit = 20 } = req.query;
|
const idapp = req.user.idapp;
|
||||||
|
const { page = 1, limit = 20 } = req.query;
|
||||||
|
|
||||||
if (!idapp) {
|
if (!idapp) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -83,7 +84,8 @@ const getMyChats = async (req, res) => {
|
|||||||
const getOrCreateDirectChat = async (req, res) => {
|
const getOrCreateDirectChat = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { idapp, otherUserId, rideId } = req.body;
|
const idapp = req.user.idapp;
|
||||||
|
const { otherUserId, rideId } = req.body;
|
||||||
|
|
||||||
if (!idapp || !otherUserId) {
|
if (!idapp || !otherUserId) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -199,7 +201,8 @@ const getChatById = async (req, res) => {
|
|||||||
const getChatMessages = async (req, res) => {
|
const getChatMessages = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { idapp, before, after, limit = 50 } = req.query;
|
const idapp = req.user.idapp;
|
||||||
|
const { before, after, limit = 50 } = req.query;
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
|
|
||||||
// Verifica accesso alla chat
|
// Verifica accesso alla chat
|
||||||
@@ -258,7 +261,8 @@ const getChatMessages = async (req, res) => {
|
|||||||
const sendMessage = async (req, res) => {
|
const sendMessage = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { idapp, text, type = 'text', metadata, replyTo } = req.body;
|
const idapp = req.user.idapp;
|
||||||
|
const { text, type = 'text', metadata, replyTo } = req.body;
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
|
|
||||||
if (!idapp || !text) {
|
if (!idapp || !text) {
|
||||||
@@ -499,7 +503,7 @@ const toggleMuteChat = async (req, res) => {
|
|||||||
const getUnreadCount = async (req, res) => {
|
const getUnreadCount = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { idapp } = req.query;
|
const idapp = req.user.idapp;
|
||||||
|
|
||||||
if (!idapp) {
|
if (!idapp) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -597,6 +601,67 @@ const deleteMessage = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Elimina una chat (soft delete)
|
||||||
|
* @route DELETE /api/trasporti/chats/:id
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
const deleteChat = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const userId = req.user._id;
|
||||||
|
|
||||||
|
const chat = await Chat.findById(id);
|
||||||
|
|
||||||
|
if (!chat) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Chat non trovata'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifica che l'utente sia partecipante
|
||||||
|
if (!chat.hasParticipant(userId)) {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Non sei autorizzato a eliminare questa chat'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soft delete: segna come non attiva per questo utente
|
||||||
|
// Se vuoi hard delete, usa: await chat.deleteOne();
|
||||||
|
|
||||||
|
// Opzione 1: Soft delete (chat rimane ma nascosta per questo utente)
|
||||||
|
if (!chat.deletedBy) {
|
||||||
|
chat.deletedBy = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chat.deletedBy.includes(userId)) {
|
||||||
|
chat.deletedBy.push(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se tutti i partecipanti hanno eliminato, marca come non attiva
|
||||||
|
if (chat.deletedBy.length === chat.participants.length) {
|
||||||
|
chat.isActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
await chat.save();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Chat eliminata'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Errore eliminazione chat:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Errore nell\'eliminazione della chat',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getMyChats,
|
getMyChats,
|
||||||
getOrCreateDirectChat,
|
getOrCreateDirectChat,
|
||||||
@@ -607,5 +672,6 @@ module.exports = {
|
|||||||
toggleBlockChat,
|
toggleBlockChat,
|
||||||
toggleMuteChat,
|
toggleMuteChat,
|
||||||
getUnreadCount,
|
getUnreadCount,
|
||||||
deleteMessage
|
deleteMessage,
|
||||||
|
deleteChat,
|
||||||
};
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
const Feedback = require('../models/Feedback');
|
const Feedback = require('../models/Feedback');
|
||||||
const Ride = require('../models/Ride');
|
const Ride = require('../models/Ride');
|
||||||
const RideRequest = require('../models/RideRequest');
|
const RideRequest = require('../models/RideRequest');
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @desc Crea un feedback per un viaggio
|
* @desc Crea un feedback per un viaggio
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ const imageGenerator = require('../services/imageGenerator');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs').promises;
|
const fs = require('fs').promises;
|
||||||
|
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || './upload';
|
||||||
|
|
||||||
const posterController = {
|
const posterController = {
|
||||||
// POST /posters
|
// POST /posters
|
||||||
@@ -396,7 +396,7 @@ const posterController = {
|
|||||||
// Aggiorna asset nel poster
|
// Aggiorna asset nel poster
|
||||||
const assetData = {
|
const assetData = {
|
||||||
sourceType: 'ai',
|
sourceType: 'ai',
|
||||||
url: `/uploads/ai-generated/${fileName}`,
|
url: `/upload/ai-generated/${fileName}`,
|
||||||
mimeType: 'image/jpeg',
|
mimeType: 'image/jpeg',
|
||||||
aiParams: {
|
aiParams: {
|
||||||
prompt,
|
prompt,
|
||||||
@@ -572,7 +572,7 @@ const posterController = {
|
|||||||
assets: {
|
assets: {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
sourceType: 'ai',
|
sourceType: 'ai',
|
||||||
url: `/uploads/ai-generated/${fileName}`,
|
url: `/upload/ai-generated/${fileName}`,
|
||||||
mimeType: 'image/jpeg',
|
mimeType: 'image/jpeg',
|
||||||
aiParams: {
|
aiParams: {
|
||||||
prompt: aiPrompt,
|
prompt: aiPrompt,
|
||||||
@@ -629,12 +629,12 @@ const posterController = {
|
|||||||
poster.setRenderOutput({
|
poster.setRenderOutput({
|
||||||
png: {
|
png: {
|
||||||
path: result.pngPath,
|
path: result.pngPath,
|
||||||
url: `/uploads/posters/final/${path.basename(result.pngPath)}`,
|
url: `/upload/posters/final/${path.basename(result.pngPath)}`,
|
||||||
size: result.pngSize
|
size: result.pngSize
|
||||||
},
|
},
|
||||||
jpg: {
|
jpg: {
|
||||||
path: result.jpgPath,
|
path: result.jpgPath,
|
||||||
url: `/uploads/posters/final/${path.basename(result.jpgPath)}`,
|
url: `/upload/posters/final/${path.basename(result.jpgPath)}`,
|
||||||
size: result.jpgSize,
|
size: result.jpgSize,
|
||||||
quality: 95
|
quality: 95
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const Ride = require('../models/Ride');
|
const Ride = require('../models/Ride');
|
||||||
const User = require('../models/User');
|
const User = require('../models/user');
|
||||||
const RideRequest = require('../models/RideRequest');
|
const RideRequest = require('../models/RideRequest');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -602,6 +602,157 @@ const getRequestById = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @desc Ottieni richieste ricevute (io come conducente)
|
||||||
|
* @route GET /api/trasporti/requests/received
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
const getReceivedRequests = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const driverId = req.user._id;
|
||||||
|
const { status, page = 1, limit = 20 } = req.query;
|
||||||
|
|
||||||
|
// Build query
|
||||||
|
const query = { driverId };
|
||||||
|
if (status) {
|
||||||
|
query.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const skip = (parseInt(page) - 1) * parseInt(limit);
|
||||||
|
|
||||||
|
// 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')
|
||||||
|
.sort({ createdAt: -1 })
|
||||||
|
.skip(skip)
|
||||||
|
.limit(parseInt(limit))
|
||||||
|
.lean();
|
||||||
|
|
||||||
|
// Get counts by status
|
||||||
|
const counts = await RideRequest.aggregate([
|
||||||
|
{ $match: { driverId } },
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: '$status',
|
||||||
|
count: { $sum: 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Format counts
|
||||||
|
const statusCounts = {
|
||||||
|
pending: 0,
|
||||||
|
accepted: 0,
|
||||||
|
rejected: 0,
|
||||||
|
cancelled: 0,
|
||||||
|
expired: 0,
|
||||||
|
completed: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
counts.forEach(c => {
|
||||||
|
if (statusCounts.hasOwnProperty(c._id)) {
|
||||||
|
statusCounts[c._id] = c.count;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Total count
|
||||||
|
const total = await RideRequest.countDocuments(query);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
requests,
|
||||||
|
counts: statusCounts,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
total,
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @desc Ottieni richieste inviate (io come passeggero)
|
||||||
|
* @route GET /api/trasporti/requests/sent
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
const getSentRequests = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const { status, page = 1, limit = 20 } = req.query;
|
||||||
|
|
||||||
|
// Build query - FIX: usa passengerId invece di userId
|
||||||
|
const query = { passengerId: userId };
|
||||||
|
if (status) {
|
||||||
|
query.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const skip = (parseInt(page) - 1) * parseInt(limit);
|
||||||
|
|
||||||
|
// Fetch requests
|
||||||
|
const requests = await RideRequest.find(query)
|
||||||
|
.populate('driverId', 'name surname profile.img')
|
||||||
|
.populate('rideId', 'departure destination departureDate availableSeats currentPassengers')
|
||||||
|
.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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return req;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Total count
|
||||||
|
const total = await RideRequest.countDocuments(query);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
requests: enrichedRequests,
|
||||||
|
pagination: {
|
||||||
|
page: parseInt(page),
|
||||||
|
limit: parseInt(limit),
|
||||||
|
total,
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createRequest,
|
createRequest,
|
||||||
getRequestsForRide,
|
getRequestsForRide,
|
||||||
@@ -611,6 +762,6 @@ module.exports = {
|
|||||||
rejectRequest,
|
rejectRequest,
|
||||||
cancelRequest,
|
cancelRequest,
|
||||||
getRequestById,
|
getRequestById,
|
||||||
getReceivedRequests: getPendingRequests,
|
getReceivedRequests,
|
||||||
getSentRequests: getMyRequests,
|
getSentRequests,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,9 +5,9 @@
|
|||||||
"sourceType": "ai",
|
"sourceType": "ai",
|
||||||
|
|
||||||
"file": {
|
"file": {
|
||||||
"path": "/uploads/assets/backgrounds/forest_autumn_001.jpg",
|
"path": "/upload/assets/backgrounds/forest_autumn_001.jpg",
|
||||||
"url": "/api/assets/asset_bg_001/file",
|
"url": "/api/assets/asset_bg_001/file",
|
||||||
"thumbnailPath": "/uploads/assets/backgrounds/thumbs/forest_autumn_001_thumb.jpg",
|
"thumbnailPath": "/upload/assets/backgrounds/thumbs/forest_autumn_001_thumb.jpg",
|
||||||
"thumbnailUrl": "/api/assets/asset_bg_001/thumbnail",
|
"thumbnailUrl": "/api/assets/asset_bg_001/thumbnail",
|
||||||
"originalName": null,
|
"originalName": null,
|
||||||
"mimeType": "image/jpeg",
|
"mimeType": "image/jpeg",
|
||||||
|
|||||||
@@ -21,8 +21,8 @@
|
|||||||
"backgroundImage": {
|
"backgroundImage": {
|
||||||
"id": "asset_bg_001",
|
"id": "asset_bg_001",
|
||||||
"sourceType": "ai",
|
"sourceType": "ai",
|
||||||
"url": "/uploads/posters/poster_sagra_2025_bg.jpg",
|
"url": "/upload/posters/poster_sagra_2025_bg.jpg",
|
||||||
"thumbnailUrl": "/uploads/posters/thumbs/poster_sagra_2025_bg_thumb.jpg",
|
"thumbnailUrl": "/upload/posters/thumbs/poster_sagra_2025_bg_thumb.jpg",
|
||||||
"mimeType": "image/jpeg",
|
"mimeType": "image/jpeg",
|
||||||
"size": 2458000,
|
"size": 2458000,
|
||||||
"dimensions": { "width": 2480, "height": 3508 },
|
"dimensions": { "width": 2480, "height": 3508 },
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
"mainImage": {
|
"mainImage": {
|
||||||
"id": "asset_main_001",
|
"id": "asset_main_001",
|
||||||
"sourceType": "upload",
|
"sourceType": "upload",
|
||||||
"url": "/uploads/assets/porcini_basket_hero.jpg",
|
"url": "/upload/assets/porcini_basket_hero.jpg",
|
||||||
"thumbnailUrl": "/uploads/assets/thumbs/porcini_basket_hero_thumb.jpg",
|
"thumbnailUrl": "/upload/assets/thumbs/porcini_basket_hero_thumb.jpg",
|
||||||
"originalName": "IMG_20241015_porcini.jpg",
|
"originalName": "IMG_20241015_porcini.jpg",
|
||||||
"mimeType": "image/jpeg",
|
"mimeType": "image/jpeg",
|
||||||
"size": 1845000,
|
"size": 1845000,
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
"id": "asset_logo_001",
|
"id": "asset_logo_001",
|
||||||
"slotId": "logo_slot_1",
|
"slotId": "logo_slot_1",
|
||||||
"sourceType": "upload",
|
"sourceType": "upload",
|
||||||
"url": "/uploads/logos/comune_borgomontano.png",
|
"url": "/upload/logos/comune_borgomontano.png",
|
||||||
"originalName": "logo_comune.png",
|
"originalName": "logo_comune.png",
|
||||||
"mimeType": "image/png",
|
"mimeType": "image/png",
|
||||||
"size": 45000
|
"size": 45000
|
||||||
@@ -63,7 +63,7 @@
|
|||||||
"id": "asset_logo_002",
|
"id": "asset_logo_002",
|
||||||
"slotId": "logo_slot_2",
|
"slotId": "logo_slot_2",
|
||||||
"sourceType": "upload",
|
"sourceType": "upload",
|
||||||
"url": "/uploads/logos/proloco_borgomontano.png",
|
"url": "/upload/logos/proloco_borgomontano.png",
|
||||||
"originalName": "logo_proloco.png",
|
"originalName": "logo_proloco.png",
|
||||||
"mimeType": "image/png",
|
"mimeType": "image/png",
|
||||||
"size": 38000
|
"size": 38000
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"id": "asset_logo_003",
|
"id": "asset_logo_003",
|
||||||
"slotId": "logo_slot_3",
|
"slotId": "logo_slot_3",
|
||||||
"sourceType": "ai",
|
"sourceType": "ai",
|
||||||
"url": "/uploads/logos/ai_generated_mushroom_logo.png",
|
"url": "/upload/logos/ai_generated_mushroom_logo.png",
|
||||||
"mimeType": "image/png",
|
"mimeType": "image/png",
|
||||||
"size": 52000,
|
"size": 52000,
|
||||||
"aiParams": {
|
"aiParams": {
|
||||||
@@ -100,12 +100,12 @@
|
|||||||
|
|
||||||
"renderOutput": {
|
"renderOutput": {
|
||||||
"png": {
|
"png": {
|
||||||
"path": "/uploads/posters/final/poster_sagra_2025_final.png",
|
"path": "/upload/posters/final/poster_sagra_2025_final.png",
|
||||||
"size": 8945000,
|
"size": 8945000,
|
||||||
"url": "/api/posters/poster_sagra_funghi_2025_001/download/png"
|
"url": "/api/posters/poster_sagra_funghi_2025_001/download/png"
|
||||||
},
|
},
|
||||||
"jpg": {
|
"jpg": {
|
||||||
"path": "/uploads/posters/final/poster_sagra_2025_final.jpg",
|
"path": "/upload/posters/final/poster_sagra_2025_final.jpg",
|
||||||
"quality": 95,
|
"quality": 95,
|
||||||
"size": 2145000,
|
"size": 2145000,
|
||||||
"url": "/api/posters/poster_sagra_funghi_2025_001/download/jpg"
|
"url": "/api/posters/poster_sagra_funghi_2025_001/download/jpg"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ const multer = require('multer');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
|
|
||||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
|
const UPLOAD_DIR = process.env.UPLOAD_DIR || './upload';
|
||||||
|
|
||||||
const storage = multer.diskStorage({
|
const storage = multer.diskStorage({
|
||||||
destination: (req, file, cb) => {
|
destination: (req, file, cb) => {
|
||||||
|
|||||||
@@ -1,86 +1,94 @@
|
|||||||
const mongoose = require('mongoose');
|
const mongoose = require('mongoose');
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
const LastMessageSchema = new Schema({
|
const LastMessageSchema = new Schema(
|
||||||
text: {
|
{
|
||||||
|
text: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
senderId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
},
|
||||||
|
timestamp: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now,
|
||||||
|
},
|
||||||
type: String,
|
type: String,
|
||||||
trim: true
|
|
||||||
},
|
},
|
||||||
senderId: {
|
{ _id: false }
|
||||||
type: Schema.Types.ObjectId,
|
);
|
||||||
ref: 'User'
|
|
||||||
},
|
|
||||||
timestamp: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: ['text', 'ride_share', 'location', 'image', 'system'],
|
|
||||||
default: 'text'
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
const ChatSchema = new Schema({
|
const ChatSchema = new Schema(
|
||||||
idapp: {
|
{
|
||||||
type: String,
|
idapp: {
|
||||||
required: true,
|
type: String,
|
||||||
index: true
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
participants: [
|
||||||
|
{
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
rideId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'Ride',
|
||||||
|
index: true,
|
||||||
|
// Opzionale: chat collegata a un viaggio specifico
|
||||||
|
},
|
||||||
|
rideRequestId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'RideRequest',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ['direct', 'ride', 'group'],
|
||||||
|
default: 'direct',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
// Solo per chat di gruppo
|
||||||
|
},
|
||||||
|
lastMessage: {
|
||||||
|
type: LastMessageSchema,
|
||||||
|
},
|
||||||
|
unreadCount: {
|
||||||
|
type: Map,
|
||||||
|
of: Number,
|
||||||
|
default: new Map(),
|
||||||
|
// { odIdUtente: numeroMessaggiNonLetti }
|
||||||
|
},
|
||||||
|
isActive: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
mutedBy: [
|
||||||
|
{
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
blockedBy: [
|
||||||
|
{
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
metadata: {
|
||||||
|
type: Schema.Types.Mixed,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
participants: [{
|
{
|
||||||
type: Schema.Types.ObjectId,
|
timestamps: true,
|
||||||
ref: 'User',
|
toJSON: { virtuals: true },
|
||||||
required: true
|
toObject: { virtuals: true },
|
||||||
}],
|
|
||||||
rideId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'Ride',
|
|
||||||
index: true
|
|
||||||
// Opzionale: chat collegata a un viaggio specifico
|
|
||||||
},
|
|
||||||
rideRequestId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'RideRequest'
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: ['direct', 'ride', 'group'],
|
|
||||||
default: 'direct'
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
// Solo per chat di gruppo
|
|
||||||
},
|
|
||||||
lastMessage: {
|
|
||||||
type: LastMessageSchema
|
|
||||||
},
|
|
||||||
unreadCount: {
|
|
||||||
type: Map,
|
|
||||||
of: Number,
|
|
||||||
default: new Map()
|
|
||||||
// { odIdUtente: numeroMessaggiNonLetti }
|
|
||||||
},
|
|
||||||
isActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
mutedBy: [{
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
}],
|
|
||||||
blockedBy: [{
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
}],
|
|
||||||
metadata: {
|
|
||||||
type: Schema.Types.Mixed
|
|
||||||
}
|
}
|
||||||
}, {
|
);
|
||||||
timestamps: true,
|
|
||||||
toJSON: { virtuals: true },
|
|
||||||
toObject: { virtuals: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Indici
|
// Indici
|
||||||
ChatSchema.index({ participants: 1 });
|
ChatSchema.index({ participants: 1 });
|
||||||
@@ -88,71 +96,89 @@ ChatSchema.index({ idapp: 1, participants: 1 });
|
|||||||
ChatSchema.index({ idapp: 1, updatedAt: -1 });
|
ChatSchema.index({ idapp: 1, updatedAt: -1 });
|
||||||
|
|
||||||
// Virtual per contare messaggi non letti totali
|
// Virtual per contare messaggi non letti totali
|
||||||
ChatSchema.virtual('totalUnread').get(function() {
|
ChatSchema.virtual('totalUnread').get(function () {
|
||||||
if (!this.unreadCount) return 0;
|
if (!this.unreadCount) return 0;
|
||||||
let total = 0;
|
let total = 0;
|
||||||
this.unreadCount.forEach(count => {
|
this.unreadCount.forEach((count) => {
|
||||||
total += count;
|
total += count;
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Metodo per ottenere unread count per un utente specifico
|
// Metodo per ottenere unread count per un utente specifico
|
||||||
ChatSchema.methods.getUnreadForUser = function(userId) {
|
ChatSchema.methods.getUnreadForUser = function (userId) {
|
||||||
if (!this.unreadCount) return 0;
|
if (!this.unreadCount) return 0;
|
||||||
return this.unreadCount.get(userId.toString()) || 0;
|
return this.unreadCount.get(userId.toString()) || 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per incrementare unread count
|
// ✅ FIX: incrementUnread (assicura conversione corretta)
|
||||||
ChatSchema.methods.incrementUnread = function(excludeUserId) {
|
ChatSchema.methods.incrementUnread = function (excludeUserId) {
|
||||||
this.participants.forEach(participantId => {
|
const excludeIdStr = excludeUserId.toString();
|
||||||
const id = participantId.toString();
|
|
||||||
if (id !== excludeUserId.toString()) {
|
this.participants.forEach((participantId) => {
|
||||||
|
// Gestisci sia ObjectId che oggetti popolati
|
||||||
|
const id = participantId._id
|
||||||
|
? participantId._id.toString()
|
||||||
|
: participantId.toString();
|
||||||
|
|
||||||
|
if (id !== excludeIdStr) {
|
||||||
const current = this.unreadCount.get(id) || 0;
|
const current = this.unreadCount.get(id) || 0;
|
||||||
this.unreadCount.set(id, current + 1);
|
this.unreadCount.set(id, current + 1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per resettare unread count per un utente
|
// Metodo per resettare unread count per un utente
|
||||||
ChatSchema.methods.markAsRead = function(userId) {
|
ChatSchema.methods.markAsRead = function (userId) {
|
||||||
this.unreadCount.set(userId.toString(), 0);
|
this.unreadCount.set(userId.toString(), 0);
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per aggiornare ultimo messaggio
|
// Metodo per aggiornare ultimo messaggio
|
||||||
ChatSchema.methods.updateLastMessage = function(message) {
|
ChatSchema.methods.updateLastMessage = function (message) {
|
||||||
this.lastMessage = {
|
this.lastMessage = {
|
||||||
text: message.text,
|
text: message.text,
|
||||||
senderId: message.senderId,
|
senderId: message.senderId,
|
||||||
timestamp: message.createdAt || new Date(),
|
timestamp: message.createdAt || new Date(),
|
||||||
type: message.type || 'text'
|
type: message.type || 'text',
|
||||||
};
|
};
|
||||||
return this.save();
|
return this.save();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per verificare se un utente è partecipante
|
// Metodo per verificare se un utente è partecipante
|
||||||
ChatSchema.methods.hasParticipant = function(userId) {
|
// ✅ FIX: Gestisce sia ObjectId che oggetti User popolati
|
||||||
return this.participants.some(
|
ChatSchema.methods.hasParticipant = function (userId) {
|
||||||
p => p.toString() === userId.toString()
|
const userIdStr = userId.toString();
|
||||||
);
|
|
||||||
|
return this.participants.some((p) => {
|
||||||
|
// Se p è un oggetto popolato (ha _id), usa p._id
|
||||||
|
// Altrimenti p è già un ObjectId
|
||||||
|
const participantId = p._id ? p._id.toString() : p.toString();
|
||||||
|
return participantId === userIdStr;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per verificare se la chat è bloccata per un utente
|
// Metodo per verificare se la chat è bloccata per un utente
|
||||||
ChatSchema.methods.isBlockedFor = function(userId) {
|
// ✅ FIX: Metodo isBlockedFor (stesso problema)
|
||||||
return this.blockedBy.some(
|
ChatSchema.methods.isBlockedFor = function (userId) {
|
||||||
id => id.toString() === userId.toString()
|
const userIdStr = userId.toString();
|
||||||
);
|
|
||||||
|
return this.blockedBy.some((id) => {
|
||||||
|
const blockedId = id._id ? id._id.toString() : id.toString();
|
||||||
|
return blockedId === userIdStr;
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Metodo statico per trovare o creare una chat diretta
|
// Metodo statico per trovare o creare una chat diretta
|
||||||
ChatSchema.statics.findOrCreateDirect = async function(idapp, userId1, userId2, rideId = null) {
|
ChatSchema.statics.findOrCreateDirect = async function (idapp, userId1, userId2, rideId = null) {
|
||||||
// Cerca chat esistente tra i due utenti
|
// Cerca chat esistente tra i due utenti
|
||||||
let chat = await this.findOne({
|
let chat = await this.findOne({
|
||||||
idapp,
|
idapp,
|
||||||
type: 'direct',
|
type: 'direct',
|
||||||
participants: { $all: [userId1, userId2], $size: 2 }
|
participants: { $all: [userId1, userId2], $size: 2 },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
@@ -161,7 +187,7 @@ ChatSchema.statics.findOrCreateDirect = async function(idapp, userId1, userId2,
|
|||||||
type: 'direct',
|
type: 'direct',
|
||||||
participants: [userId1, userId2],
|
participants: [userId1, userId2],
|
||||||
rideId,
|
rideId,
|
||||||
unreadCount: new Map()
|
unreadCount: new Map(),
|
||||||
});
|
});
|
||||||
await chat.save();
|
await chat.save();
|
||||||
} else if (rideId && !chat.rideId) {
|
} else if (rideId && !chat.rideId) {
|
||||||
@@ -174,27 +200,27 @@ ChatSchema.statics.findOrCreateDirect = async function(idapp, userId1, userId2,
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Metodo statico per ottenere tutte le chat di un utente
|
// Metodo statico per ottenere tutte le chat di un utente
|
||||||
ChatSchema.statics.getChatsForUser = function(idapp, userId) {
|
ChatSchema.statics.getChatsForUser = function (idapp, userId) {
|
||||||
return this.find({
|
return this.find({
|
||||||
idapp,
|
idapp,
|
||||||
participants: userId,
|
participants: userId,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
blockedBy: { $ne: userId }
|
blockedBy: { $ne: userId },
|
||||||
})
|
})
|
||||||
.populate('participants', 'username name surname profile.avatar')
|
.populate('participants', 'username name surname profile.avatar')
|
||||||
.populate('rideId', 'departure destination dateTime')
|
.populate('rideId', 'departure destination dateTime')
|
||||||
.sort({ updatedAt: -1 });
|
.sort({ updatedAt: -1 });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo statico per creare chat di gruppo per un viaggio
|
// Metodo statico per creare chat di gruppo per un viaggio
|
||||||
ChatSchema.statics.createRideGroupChat = async function(idapp, rideId, title, participantIds) {
|
ChatSchema.statics.createRideGroupChat = async function (idapp, rideId, title, participantIds) {
|
||||||
const chat = new this({
|
const chat = new this({
|
||||||
idapp,
|
idapp,
|
||||||
type: 'group',
|
type: 'group',
|
||||||
rideId,
|
rideId,
|
||||||
title,
|
title,
|
||||||
participants: participantIds,
|
participants: participantIds,
|
||||||
unreadCount: new Map()
|
unreadCount: new Map(),
|
||||||
});
|
});
|
||||||
return chat.save();
|
return chat.save();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -168,8 +168,10 @@ MessageSchema.methods.editText = function(newText) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Metodo statico per ottenere messaggi di una chat con paginazione
|
// Metodo statico per ottenere messaggi di una chat con paginazione
|
||||||
MessageSchema.statics.getByChat = function(idapp, chatId, options = {}) {
|
// Message.js (model)
|
||||||
const { limit = 50, before = null, after = null } = options;
|
|
||||||
|
MessageSchema.statics.getByChat = async function(idapp, chatId, options = {}) {
|
||||||
|
const { limit = 50, before, after } = options;
|
||||||
|
|
||||||
const query = {
|
const query = {
|
||||||
idapp,
|
idapp,
|
||||||
@@ -177,6 +179,7 @@ MessageSchema.statics.getByChat = function(idapp, chatId, options = {}) {
|
|||||||
isDeleted: false
|
isDeleted: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Filtra per timestamp
|
||||||
if (before) {
|
if (before) {
|
||||||
query.createdAt = { $lt: new Date(before) };
|
query.createdAt = { $lt: new Date(before) };
|
||||||
}
|
}
|
||||||
@@ -184,12 +187,12 @@ MessageSchema.statics.getByChat = function(idapp, chatId, options = {}) {
|
|||||||
query.createdAt = { $gt: new Date(after) };
|
query.createdAt = { $gt: new Date(after) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ✅ Sempre in ordine decrescente (dal più recente al più vecchio)
|
||||||
return this.find(query)
|
return this.find(query)
|
||||||
.populate('senderId', 'username name surname profile.avatar')
|
.populate('senderId', 'username name surname profile.img')
|
||||||
.populate('replyTo', 'text senderId')
|
.populate('replyTo', 'text senderId')
|
||||||
.populate('metadata.rideId', 'departure destination dateTime')
|
.sort({ createdAt: -1 }) // -1 = più recente prima
|
||||||
.sort({ createdAt: -1 })
|
.limit(limit)
|
||||||
.limit(limit);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo statico per creare messaggio di sistema
|
// Metodo statico per creare messaggio di sistema
|
||||||
|
|||||||
@@ -2,377 +2,417 @@ const mongoose = require('mongoose');
|
|||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
|
||||||
// Schema per le coordinate geografiche
|
// Schema per le coordinate geografiche
|
||||||
const CoordinatesSchema = new Schema({
|
const CoordinatesSchema = new Schema(
|
||||||
lat: {
|
{
|
||||||
type: Number,
|
lat: {
|
||||||
required: true
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
lng: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
lng: {
|
{ _id: false }
|
||||||
type: Number,
|
);
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per una località (partenza, destinazione, waypoint)
|
// Schema per una località (partenza, destinazione, waypoint)
|
||||||
const LocationSchema = new Schema({
|
const LocationSchema = new Schema(
|
||||||
city: {
|
{
|
||||||
type: String,
|
city: {
|
||||||
required: true,
|
type: String,
|
||||||
trim: true
|
required: true,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
province: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
region: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
country: {
|
||||||
|
type: String,
|
||||||
|
default: 'Italia',
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
postalCode: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
coordinates: {
|
||||||
|
type: CoordinatesSchema,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
address: {
|
{ _id: false }
|
||||||
type: String,
|
);
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
province: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
region: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
country: {
|
|
||||||
type: String,
|
|
||||||
default: 'Italia',
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
postalCode: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
coordinates: {
|
|
||||||
type: CoordinatesSchema,
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per i waypoint (tappe intermedie)
|
// Schema per i waypoint (tappe intermedie)
|
||||||
const WaypointSchema = new Schema({
|
const WaypointSchema = new Schema({
|
||||||
location: {
|
location: {
|
||||||
type: LocationSchema,
|
type: LocationSchema,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
estimatedArrival: {
|
estimatedArrival: {
|
||||||
type: Date
|
type: Date,
|
||||||
},
|
},
|
||||||
stopDuration: {
|
stopDuration: {
|
||||||
type: Number, // minuti di sosta
|
type: Number, // minuti di sosta
|
||||||
default: 0
|
default: 0,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schema per la ricorrenza del viaggio
|
// Schema per la ricorrenza del viaggio
|
||||||
const RecurrenceSchema = new Schema({
|
const RecurrenceSchema = new Schema(
|
||||||
type: {
|
{
|
||||||
type: String,
|
type: {
|
||||||
enum: ['once', 'weekly', 'custom_days', 'custom_dates'],
|
type: String,
|
||||||
default: 'once'
|
enum: ['once', 'weekly', 'custom_days', 'custom_dates'],
|
||||||
|
default: 'once',
|
||||||
|
},
|
||||||
|
daysOfWeek: [
|
||||||
|
{
|
||||||
|
type: Number,
|
||||||
|
min: 0,
|
||||||
|
max: 6,
|
||||||
|
// 0 = Domenica, 1 = Lunedì, ..., 6 = Sabato
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customDates: [
|
||||||
|
{
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
startDate: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
excludedDates: [
|
||||||
|
{
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
daysOfWeek: [{
|
{ _id: false }
|
||||||
type: Number,
|
);
|
||||||
min: 0,
|
|
||||||
max: 6
|
|
||||||
// 0 = Domenica, 1 = Lunedì, ..., 6 = Sabato
|
|
||||||
}],
|
|
||||||
customDates: [{
|
|
||||||
type: Date
|
|
||||||
}],
|
|
||||||
startDate: {
|
|
||||||
type: Date
|
|
||||||
},
|
|
||||||
endDate: {
|
|
||||||
type: Date
|
|
||||||
},
|
|
||||||
excludedDates: [{
|
|
||||||
type: Date
|
|
||||||
}]
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per i passeggeri
|
// Schema per i passeggeri
|
||||||
const PassengersSchema = new Schema({
|
const PassengersSchema = new Schema(
|
||||||
available: {
|
{
|
||||||
type: Number,
|
available: {
|
||||||
required: true,
|
type: Number,
|
||||||
min: 0
|
required: true,
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
min: 1,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
max: {
|
{ _id: false }
|
||||||
type: Number,
|
);
|
||||||
required: true,
|
|
||||||
min: 1
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per il veicolo
|
// Schema per il veicolo
|
||||||
const VehicleSchema = new Schema({
|
const VehicleSchema = new Schema(
|
||||||
type: {
|
{
|
||||||
type: String,
|
type: {
|
||||||
enum: ['auto', 'moto', 'furgone', 'minibus', 'altro'],
|
type: String,
|
||||||
default: 'auto'
|
enum: ['auto', 'moto', 'furgone', 'minibus', 'altro'],
|
||||||
|
default: 'auto',
|
||||||
|
},
|
||||||
|
brand: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
model: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
color: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
colorHex: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
type: Number,
|
||||||
|
},
|
||||||
|
licensePlate: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
seats: {
|
||||||
|
type: Number,
|
||||||
|
min: 1,
|
||||||
|
},
|
||||||
|
photos: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi'],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
brand: {
|
{ _id: false }
|
||||||
type: String,
|
);
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
model: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
colorHex: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
year: {
|
|
||||||
type: Number
|
|
||||||
},
|
|
||||||
licensePlate: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
seats: {
|
|
||||||
type: Number,
|
|
||||||
min: 1
|
|
||||||
},
|
|
||||||
features: [{
|
|
||||||
type: String,
|
|
||||||
enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi']
|
|
||||||
}]
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per le preferenze di viaggio
|
// Schema per le preferenze di viaggio
|
||||||
const RidePreferencesSchema = new Schema({
|
const RidePreferencesSchema = new Schema(
|
||||||
smoking: {
|
{
|
||||||
type: String,
|
smoking: {
|
||||||
enum: ['yes', 'no', 'outside_only'],
|
type: String,
|
||||||
default: 'no'
|
enum: ['yes', 'no', 'outside_only'],
|
||||||
|
default: 'no',
|
||||||
|
},
|
||||||
|
pets: {
|
||||||
|
type: String,
|
||||||
|
enum: ['no', 'small', 'medium', 'large', 'all'],
|
||||||
|
default: 'no',
|
||||||
|
},
|
||||||
|
luggage: {
|
||||||
|
type: String,
|
||||||
|
enum: ['none', 'small', 'medium', 'large'],
|
||||||
|
default: 'medium',
|
||||||
|
},
|
||||||
|
packages: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
maxPackageSize: {
|
||||||
|
type: String,
|
||||||
|
enum: ['small', 'medium', 'large', 'xlarge'],
|
||||||
|
default: 'medium',
|
||||||
|
},
|
||||||
|
music: {
|
||||||
|
type: String,
|
||||||
|
enum: ['no_music', 'quiet', 'moderate', 'loud', 'passenger_choice'],
|
||||||
|
default: 'moderate',
|
||||||
|
},
|
||||||
|
conversation: {
|
||||||
|
type: String,
|
||||||
|
enum: ['quiet', 'moderate', 'chatty'],
|
||||||
|
default: 'moderate',
|
||||||
|
},
|
||||||
|
foodAllowed: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
childrenFriendly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
wheelchairAccessible: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
otherPreferences: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
maxlength: 500,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pets: {
|
{ _id: false }
|
||||||
type: String,
|
);
|
||||||
enum: ['no', 'small', 'medium', 'large', 'all'],
|
|
||||||
default: 'no'
|
|
||||||
},
|
|
||||||
luggage: {
|
|
||||||
type: String,
|
|
||||||
enum: ['none', 'small', 'medium', 'large'],
|
|
||||||
default: 'medium'
|
|
||||||
},
|
|
||||||
packages: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
maxPackageSize: {
|
|
||||||
type: String,
|
|
||||||
enum: ['small', 'medium', 'large', 'xlarge'],
|
|
||||||
default: 'medium'
|
|
||||||
},
|
|
||||||
music: {
|
|
||||||
type: String,
|
|
||||||
enum: ['no_music', 'quiet', 'moderate', 'loud', 'passenger_choice'],
|
|
||||||
default: 'moderate'
|
|
||||||
},
|
|
||||||
conversation: {
|
|
||||||
type: String,
|
|
||||||
enum: ['quiet', 'moderate', 'chatty'],
|
|
||||||
default: 'moderate'
|
|
||||||
},
|
|
||||||
foodAllowed: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
childrenFriendly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
wheelchairAccessible: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
otherPreferences: {
|
|
||||||
type: String,
|
|
||||||
trim: true,
|
|
||||||
maxlength: 500
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema per il contributo/pagamento
|
// Schema per il contributo/pagamento
|
||||||
const ContributionItemSchema = new Schema({
|
const ContributionItemSchema = new Schema({
|
||||||
contribTypeId: {
|
contribTypeId: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: 'Contribtype',
|
ref: 'Contribtype',
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
price: {
|
price: {
|
||||||
type: Number,
|
type: Number,
|
||||||
min: 0
|
min: 0,
|
||||||
},
|
},
|
||||||
pricePerKm: {
|
pricePerKm: {
|
||||||
type: Number,
|
type: Number,
|
||||||
min: 0
|
|
||||||
},
|
|
||||||
notes: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ContributionSchema = new Schema({
|
|
||||||
contribTypes: [ContributionItemSchema],
|
|
||||||
negotiable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true
|
|
||||||
},
|
|
||||||
freeForStudents: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
freeForElders: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
}, { _id: false });
|
|
||||||
|
|
||||||
// Schema principale del Ride
|
|
||||||
const RideSchema = new Schema({
|
|
||||||
idapp: {
|
|
||||||
type: String,
|
|
||||||
required: true,
|
|
||||||
index: true
|
|
||||||
},
|
|
||||||
userId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User',
|
|
||||||
required: true,
|
|
||||||
index: true
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: String,
|
|
||||||
enum: ['offer', 'request'],
|
|
||||||
required: true,
|
|
||||||
index: true
|
|
||||||
// offer = 🟢 Offerta passaggio (sono conducente)
|
|
||||||
// request = 🔴 Richiesta passaggio (cerco passaggio)
|
|
||||||
},
|
|
||||||
departure: {
|
|
||||||
type: LocationSchema,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
destination: {
|
|
||||||
type: LocationSchema,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
waypoints: [WaypointSchema],
|
|
||||||
dateTime: {
|
|
||||||
type: Date,
|
|
||||||
required: true,
|
|
||||||
index: true
|
|
||||||
},
|
|
||||||
flexibleTime: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
flexibleMinutes: {
|
|
||||||
type: Number,
|
|
||||||
default: 30,
|
|
||||||
min: 0,
|
min: 0,
|
||||||
max: 180
|
|
||||||
},
|
|
||||||
recurrence: {
|
|
||||||
type: RecurrenceSchema,
|
|
||||||
default: () => ({ type: 'once' })
|
|
||||||
},
|
|
||||||
passengers: {
|
|
||||||
type: PassengersSchema,
|
|
||||||
required: function() {
|
|
||||||
return this.type === 'offer';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
seatsNeeded: {
|
|
||||||
type: Number,
|
|
||||||
min: 1,
|
|
||||||
default: 1,
|
|
||||||
// Solo per type = 'request'
|
|
||||||
},
|
|
||||||
vehicle: {
|
|
||||||
type: VehicleSchema,
|
|
||||||
required: function() {
|
|
||||||
return this.type === 'offer';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
preferences: {
|
|
||||||
type: RidePreferencesSchema,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
contribution: {
|
|
||||||
type: ContributionSchema,
|
|
||||||
default: () => ({ contribTypes: [] })
|
|
||||||
},
|
|
||||||
status: {
|
|
||||||
type: String,
|
|
||||||
enum: ['draft', 'active', 'full', 'in_progress', 'completed', 'cancelled', 'expired'],
|
|
||||||
default: 'active',
|
|
||||||
index: true
|
|
||||||
},
|
|
||||||
estimatedDistance: {
|
|
||||||
type: Number, // in km
|
|
||||||
min: 0
|
|
||||||
},
|
|
||||||
estimatedDuration: {
|
|
||||||
type: Number, // in minuti
|
|
||||||
min: 0
|
|
||||||
},
|
|
||||||
routePolyline: {
|
|
||||||
type: String // Polyline encoded per visualizzare il percorso
|
|
||||||
},
|
|
||||||
confirmedPassengers: [{
|
|
||||||
userId: {
|
|
||||||
type: Schema.Types.ObjectId,
|
|
||||||
ref: 'User'
|
|
||||||
},
|
|
||||||
seats: {
|
|
||||||
type: Number,
|
|
||||||
default: 1
|
|
||||||
},
|
|
||||||
pickupPoint: LocationSchema,
|
|
||||||
dropoffPoint: LocationSchema,
|
|
||||||
confirmedAt: {
|
|
||||||
type: Date,
|
|
||||||
default: Date.now
|
|
||||||
}
|
|
||||||
}],
|
|
||||||
views: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
},
|
|
||||||
isFeatured: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
},
|
||||||
notes: {
|
notes: {
|
||||||
type: String,
|
type: String,
|
||||||
trim: true,
|
trim: true,
|
||||||
maxlength: 1000
|
|
||||||
},
|
},
|
||||||
cancellationReason: {
|
|
||||||
type: String,
|
|
||||||
trim: true
|
|
||||||
},
|
|
||||||
cancelledAt: {
|
|
||||||
type: Date
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
timestamps: true,
|
|
||||||
toJSON: { virtuals: true },
|
|
||||||
toObject: { virtuals: true }
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ContributionSchema = new Schema(
|
||||||
|
{
|
||||||
|
contribTypes: [ContributionItemSchema],
|
||||||
|
negotiable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
freeForStudents: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
freeForElders: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ _id: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Schema principale del Ride
|
||||||
|
const RideSchema = new Schema(
|
||||||
|
{
|
||||||
|
idapp: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
userId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
enum: ['offer', 'request'],
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
// offer = 🟢 Offerta passaggio (sono conducente)
|
||||||
|
// request = 🔴 Richiesta passaggio (cerco passaggio)
|
||||||
|
},
|
||||||
|
departure: {
|
||||||
|
type: LocationSchema,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
destination: {
|
||||||
|
type: LocationSchema,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
waypoints: [WaypointSchema],
|
||||||
|
dateTime: {
|
||||||
|
type: Date,
|
||||||
|
required: true,
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
flexibleTime: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
flexibleMinutes: {
|
||||||
|
type: Number,
|
||||||
|
default: 30,
|
||||||
|
min: 0,
|
||||||
|
max: 180,
|
||||||
|
},
|
||||||
|
recurrence: {
|
||||||
|
type: RecurrenceSchema,
|
||||||
|
default: () => ({ type: 'once' }),
|
||||||
|
},
|
||||||
|
passengers: {
|
||||||
|
type: PassengersSchema,
|
||||||
|
required: function () {
|
||||||
|
return this.type === 'offer';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
seatsNeeded: {
|
||||||
|
type: Number,
|
||||||
|
min: 1,
|
||||||
|
default: 1,
|
||||||
|
// Solo per type = 'request'
|
||||||
|
},
|
||||||
|
vehicle: {
|
||||||
|
type: VehicleSchema,
|
||||||
|
required: function () {
|
||||||
|
return this.type === 'offer';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
type: RidePreferencesSchema,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
contribution: {
|
||||||
|
type: ContributionSchema,
|
||||||
|
default: () => ({ contribTypes: [] }),
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
type: String,
|
||||||
|
enum: ['draft', 'active', 'full', 'in_progress', 'completed', 'cancelled', 'expired'],
|
||||||
|
default: 'active',
|
||||||
|
index: true,
|
||||||
|
},
|
||||||
|
estimatedDistance: {
|
||||||
|
type: Number, // in km
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
estimatedDuration: {
|
||||||
|
type: Number, // in minuti
|
||||||
|
min: 0,
|
||||||
|
},
|
||||||
|
routePolyline: {
|
||||||
|
type: String, // Polyline encoded per visualizzare il percorso
|
||||||
|
},
|
||||||
|
confirmedPassengers: [
|
||||||
|
{
|
||||||
|
userId: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: 'User',
|
||||||
|
},
|
||||||
|
seats: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
pickupPoint: LocationSchema,
|
||||||
|
dropoffPoint: LocationSchema,
|
||||||
|
confirmedAt: {
|
||||||
|
type: Date,
|
||||||
|
default: Date.now,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
views: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
isFeatured: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
notes: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
maxlength: 1000,
|
||||||
|
},
|
||||||
|
cancellationReason: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
cancelledAt: {
|
||||||
|
type: Date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
toJSON: { virtuals: true },
|
||||||
|
toObject: { virtuals: true },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Indici per ricerche ottimizzate
|
// Indici per ricerche ottimizzate
|
||||||
RideSchema.index({ 'departure.city': 1, 'destination.city': 1 });
|
RideSchema.index({ 'departure.city': 1, 'destination.city': 1 });
|
||||||
RideSchema.index({ 'departure.coordinates': '2dsphere' });
|
RideSchema.index({ 'departure.coordinates': '2dsphere' });
|
||||||
@@ -382,41 +422,41 @@ RideSchema.index({ dateTime: 1, status: 1 });
|
|||||||
RideSchema.index({ idapp: 1, status: 1, dateTime: 1 });
|
RideSchema.index({ idapp: 1, status: 1, dateTime: 1 });
|
||||||
|
|
||||||
// Virtual per verificare se il viaggio è pieno
|
// Virtual per verificare se il viaggio è pieno
|
||||||
RideSchema.virtual('isFull').get(function() {
|
RideSchema.virtual('isFull').get(function () {
|
||||||
if (this.type === 'request') return false;
|
if (this.type === 'request') return false;
|
||||||
|
// ⚠️ CONTROLLO: verifica che passengers esista
|
||||||
|
if (!this.passengers || typeof this.passengers.available === 'undefined') return false;
|
||||||
return this.passengers.available <= 0;
|
return this.passengers.available <= 0;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Virtual per calcolare posti occupati
|
// Virtual per calcolare posti occupati
|
||||||
RideSchema.virtual('bookedSeats').get(function() {
|
RideSchema.virtual('bookedSeats').get(function () {
|
||||||
if (!this.confirmedPassengers) return 0;
|
if (!this.confirmedPassengers) return 0;
|
||||||
return this.confirmedPassengers.reduce((sum, p) => sum + (p.seats || 1), 0);
|
return this.confirmedPassengers.reduce((sum, p) => sum + (p.seats || 1), 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Virtual per ottenere tutte le città del percorso
|
// Virtual per ottenere tutte le città del percorso
|
||||||
RideSchema.virtual('allCities').get(function() {
|
RideSchema.virtual('allCities').get(function () {
|
||||||
const cities = [this.departure.city];
|
const cities = [this.departure.city];
|
||||||
if (this.waypoints && this.waypoints.length > 0) {
|
if (this.waypoints && this.waypoints.length > 0) {
|
||||||
this.waypoints
|
this.waypoints.sort((a, b) => a.order - b.order).forEach((wp) => cities.push(wp.location.city));
|
||||||
.sort((a, b) => a.order - b.order)
|
|
||||||
.forEach(wp => cities.push(wp.location.city));
|
|
||||||
}
|
}
|
||||||
cities.push(this.destination.city);
|
cities.push(this.destination.city);
|
||||||
return cities;
|
return cities;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Metodo per verificare se passa per una città
|
// Metodo per verificare se passa per una città
|
||||||
RideSchema.methods.passesThrough = function(cityName) {
|
RideSchema.methods.passesThrough = function (cityName) {
|
||||||
const normalizedCity = cityName.toLowerCase().trim();
|
const normalizedCity = cityName.toLowerCase().trim();
|
||||||
return this.allCities.some(city =>
|
return this.allCities.some(
|
||||||
city.toLowerCase().trim().includes(normalizedCity) ||
|
(city) => city.toLowerCase().trim().includes(normalizedCity) || normalizedCity.includes(city.toLowerCase().trim())
|
||||||
normalizedCity.includes(city.toLowerCase().trim())
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Metodo per aggiornare posti disponibili
|
// Metodo per aggiornare posti disponibili
|
||||||
RideSchema.methods.updateAvailableSeats = function() {
|
RideSchema.methods.updateAvailableSeats = function () {
|
||||||
if (this.type === 'offer') {
|
// ⚠️ CONTROLLO: verifica che sia un'offerta e che passengers esista
|
||||||
|
if (this.type === 'offer' && this.passengers) {
|
||||||
const booked = this.bookedSeats;
|
const booked = this.bookedSeats;
|
||||||
this.passengers.available = this.passengers.max - booked;
|
this.passengers.available = this.passengers.max - booked;
|
||||||
if (this.passengers.available <= 0) {
|
if (this.passengers.available <= 0) {
|
||||||
@@ -429,9 +469,9 @@ RideSchema.methods.updateAvailableSeats = function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Pre-save hook
|
// Pre-save hook
|
||||||
RideSchema.pre('save', function(next) {
|
RideSchema.pre('save', function (next) {
|
||||||
// Aggiorna posti disponibili se necessario
|
// ⚠️ CONTROLLO: Aggiorna posti disponibili solo se è un'offerta e passengers esiste
|
||||||
if (this.type === 'offer' && this.isModified('confirmedPassengers')) {
|
if (this.type === 'offer' && this.passengers && this.isModified('confirmedPassengers')) {
|
||||||
const booked = this.confirmedPassengers.reduce((sum, p) => sum + (p.seats || 1), 0);
|
const booked = this.confirmedPassengers.reduce((sum, p) => sum + (p.seats || 1), 0);
|
||||||
this.passengers.available = this.passengers.max - booked;
|
this.passengers.available = this.passengers.max - booked;
|
||||||
if (this.passengers.available <= 0) {
|
if (this.passengers.available <= 0) {
|
||||||
@@ -442,11 +482,11 @@ RideSchema.pre('save', function(next) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Metodi statici per ricerche comuni
|
// Metodi statici per ricerche comuni
|
||||||
RideSchema.statics.findActiveByCity = function(idapp, departureCity, destinationCity, options = {}) {
|
RideSchema.statics.findActiveByCity = function (idapp, departureCity, destinationCity, options = {}) {
|
||||||
const query = {
|
const query = {
|
||||||
idapp,
|
idapp,
|
||||||
status: { $in: ['active', 'full'] },
|
status: { $in: ['active', 'full'] },
|
||||||
dateTime: { $gte: new Date() }
|
dateTime: { $gte: new Date() },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (departureCity) {
|
if (departureCity) {
|
||||||
@@ -472,17 +512,13 @@ RideSchema.statics.findActiveByCity = function(idapp, departureCity, destination
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Ricerca viaggi che passano per una città intermedia
|
// Ricerca viaggi che passano per una città intermedia
|
||||||
RideSchema.statics.findPassingThrough = function(idapp, cityName, options = {}) {
|
RideSchema.statics.findPassingThrough = function (idapp, cityName, options = {}) {
|
||||||
const cityRegex = new RegExp(cityName, 'i');
|
const cityRegex = new RegExp(cityName, 'i');
|
||||||
const query = {
|
const query = {
|
||||||
idapp,
|
idapp,
|
||||||
status: { $in: ['active'] },
|
status: { $in: ['active'] },
|
||||||
dateTime: { $gte: new Date() },
|
dateTime: { $gte: new Date() },
|
||||||
$or: [
|
$or: [{ 'departure.city': cityRegex }, { 'destination.city': cityRegex }, { 'waypoints.location.city': cityRegex }],
|
||||||
{ 'departure.city': cityRegex },
|
|
||||||
{ 'destination.city': cityRegex },
|
|
||||||
{ 'waypoints.location.city': cityRegex }
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.type) {
|
if (options.type) {
|
||||||
|
|||||||
@@ -643,6 +643,12 @@ const UserSchema = new mongoose.Schema(
|
|||||||
enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi'],
|
enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
photos: [
|
||||||
|
{
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
isDefault: {
|
isDefault: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -7336,7 +7342,6 @@ const FuncUsers = {
|
|||||||
|
|
||||||
UserSchema.index({ 'tokens.token': 1, 'tokens.access': 1, idapp: 1, deleted: 1, updatedAt: 1 });
|
UserSchema.index({ 'tokens.token': 1, 'tokens.access': 1, idapp: 1, deleted: 1, updatedAt: 1 });
|
||||||
|
|
||||||
|
|
||||||
const User = mongoose.models.User || mongoose.model('User', UserSchema);
|
const User = mongoose.models.User || mongoose.model('User', UserSchema);
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const { Contribtype } = require('../models/Contribtype'); // Adatta al tuo path
|
const multer = require('multer');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const tools = require('../tools/general');
|
||||||
|
|
||||||
|
const { Contribtype } = require('../models/contribtype'); // Adatta al tuo path
|
||||||
|
|
||||||
// Import Controllers
|
// Import Controllers
|
||||||
const rideController = require('../controllers/rideController');
|
const rideController = require('../controllers/rideController');
|
||||||
@@ -113,7 +119,6 @@ router.get('/stats/summary', authenticate, rideController.getStatsSummary);
|
|||||||
*/
|
*/
|
||||||
router.get('/cities/suggestions', rideController.getCitySuggestions);
|
router.get('/cities/suggestions', rideController.getCitySuggestions);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route GET /api/trasporti/cities/recents
|
* @route GET /api/trasporti/cities/recents
|
||||||
* @desc città recenti per autocomplete
|
* @desc città recenti per autocomplete
|
||||||
@@ -258,6 +263,13 @@ router.put('/chats/:id/mute', authenticate, chatController.toggleMuteChat);
|
|||||||
*/
|
*/
|
||||||
router.delete('/chats/:chatId/messages/:messageId', authenticate, chatController.deleteMessage);
|
router.delete('/chats/:chatId/messages/:messageId', authenticate, chatController.deleteMessage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route DELETE /api/trasporti/chats/:id
|
||||||
|
* @desc Elimina chat (soft delete)
|
||||||
|
* @access Private (solo partecipanti)
|
||||||
|
*/
|
||||||
|
router.delete('/chats/:id', authenticate, chatController.deleteChat);
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ⭐ FEEDBACK - Recensioni
|
// ⭐ FEEDBACK - Recensioni
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -390,16 +402,16 @@ router.get('/geo/suggest-waypoints', geocodingController.suggestWaypoints);
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route GET /api/trasporti/driver/:userId
|
* @route GET /api/trasporti/driver/user/:userId
|
||||||
* @desc Profilo pubblico del conducente
|
* @desc Profilo pubblico del conducente
|
||||||
* @access Public
|
* @access Public
|
||||||
*/
|
*/
|
||||||
router.get('/driver/:userId', async (req, res) => {
|
router.get('/driver/user/:userId', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.params;
|
const { userId } = req.params;
|
||||||
const { idapp } = req.query;
|
const { idapp } = req.query;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
const Ride = require('../models/Ride');
|
const Ride = require('../models/Ride');
|
||||||
const Feedback = require('../models/Feedback');
|
const Feedback = require('../models/Feedback');
|
||||||
|
|
||||||
@@ -489,6 +501,43 @@ router.get('/driver/:userId', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route GET /api/trasporti/driver/vehicles
|
||||||
|
* @desc Ottieni veicoli dell'utente corrente
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
router.get('/driver/vehicles', authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { idapp } = req.query;
|
||||||
|
const userId = req.user._id; // Assumo che ci sia un middleware di autenticazione
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Autenticazione richiesta',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
|
const user = await User.findById(userId).select('profile.driverProfile.vehicles');
|
||||||
|
|
||||||
|
const vehicles = user?.profile?.driverProfile?.vehicles || [];
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: vehicles,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Errore recupero veicoli:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Errore durante il recupero dei veicoli',
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route PUT /api/trasporti/driver/profile
|
* @route PUT /api/trasporti/driver/profile
|
||||||
* @desc Aggiorna profilo conducente
|
* @desc Aggiorna profilo conducente
|
||||||
@@ -499,7 +548,7 @@ router.put('/driver/profile', authenticate, async (req, res) => {
|
|||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { idapp, driverProfile, preferences } = req.body;
|
const { idapp, driverProfile, preferences } = req.body;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
const updateData = {};
|
const updateData = {};
|
||||||
|
|
||||||
@@ -543,9 +592,9 @@ router.put('/driver/profile', authenticate, async (req, res) => {
|
|||||||
router.post('/driver/vehicles', authenticate, async (req, res) => {
|
router.post('/driver/vehicles', authenticate, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { vehicle } = req.body;
|
const vehicle = req.body;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
const user = await User.findByIdAndUpdate(
|
const user = await User.findByIdAndUpdate(
|
||||||
userId,
|
userId,
|
||||||
@@ -580,9 +629,9 @@ router.put('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { vehicleId } = req.params;
|
const { vehicleId } = req.params;
|
||||||
const { vehicle } = req.body;
|
const vehicle = req.body;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
const user = await User.findOneAndUpdate(
|
const user = await User.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
@@ -617,6 +666,54 @@ router.put('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route GET /api/trasporti/driver/vehicles/:vehicleId
|
||||||
|
* @desc Ottieni dettagli di un veicolo specifico
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
router.get('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const { vehicleId } = req.params;
|
||||||
|
|
||||||
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: userId,
|
||||||
|
'profile.driverProfile.vehicles._id': vehicleId,
|
||||||
|
}).select('profile.driverProfile.vehicles');
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Veicolo non trovato',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trova il veicolo specifico nell'array
|
||||||
|
const vehicle = user.profile.driverProfile.vehicles.find((v) => v._id.toString() === vehicleId);
|
||||||
|
|
||||||
|
if (!vehicle) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Veicolo non trovato',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
data: vehicle,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Errore recupero veicolo:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Errore durante il recupero del veicolo',
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @route DELETE /api/trasporti/driver/vehicles/:vehicleId
|
* @route DELETE /api/trasporti/driver/vehicles/:vehicleId
|
||||||
* @desc Rimuovi veicolo
|
* @desc Rimuovi veicolo
|
||||||
@@ -627,7 +724,7 @@ router.delete('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
|
|||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { vehicleId } = req.params;
|
const { vehicleId } = req.params;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
await User.findByIdAndUpdate(userId, {
|
await User.findByIdAndUpdate(userId, {
|
||||||
$pull: { 'profile.driverProfile.vehicles': { _id: vehicleId } },
|
$pull: { 'profile.driverProfile.vehicles': { _id: vehicleId } },
|
||||||
@@ -657,7 +754,7 @@ router.post('/driver/vehicles/:vehicleId/default', authenticate, async (req, res
|
|||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const { vehicleId } = req.params;
|
const { vehicleId } = req.params;
|
||||||
|
|
||||||
const { User } = require('../models/User');
|
const { User } = require('../models/user');
|
||||||
|
|
||||||
// Prima rimuovi isDefault da tutti
|
// Prima rimuovi isDefault da tutti
|
||||||
await User.updateOne({ _id: userId }, { $set: { 'profile.driverProfile.vehicles.$[].isDefault': false } });
|
await User.updateOne({ _id: userId }, { $set: { 'profile.driverProfile.vehicles.$[].isDefault': false } });
|
||||||
@@ -711,4 +808,211 @@ router.get('/contrib-types', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const vehiclePhotoStorage = multer.diskStorage({
|
||||||
|
destination: (req, file, cb) => {
|
||||||
|
const webServerDir = tools.getdirByIdApp(req.user.idapp) + '/upload/vehicles';
|
||||||
|
if (!fs.existsSync(webServerDir)) {
|
||||||
|
fs.mkdirSync(webServerDir, { recursive: true });
|
||||||
|
}
|
||||||
|
console.log('📁 Destinazione foto veicolo:', webServerDir);
|
||||||
|
cb(null, webServerDir);
|
||||||
|
},
|
||||||
|
filename: (req, file, cb) => {
|
||||||
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
||||||
|
const ext = path.extname(file.originalname);
|
||||||
|
const filename = 'vehicle-' + uniqueSuffix + ext;
|
||||||
|
console.log('📝 Nome file generato:', filename);
|
||||||
|
cb(null, filename);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadVehiclePhoto = multer({
|
||||||
|
storage: vehiclePhotoStorage,
|
||||||
|
limits: {
|
||||||
|
fileSize: 5 * 1024 * 1024, // 5MB
|
||||||
|
},
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
console.log('🔍 File ricevuto:', {
|
||||||
|
fieldname: file.fieldname,
|
||||||
|
originalname: file.originalname,
|
||||||
|
mimetype: file.mimetype,
|
||||||
|
});
|
||||||
|
|
||||||
|
const allowedMimes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp', 'image/gif'];
|
||||||
|
if (allowedMimes.includes(file.mimetype)) {
|
||||||
|
cb(null, true);
|
||||||
|
} else {
|
||||||
|
cb(new Error('Solo immagini sono ammesse'), false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route POST /api/trasporti/upload/vehicle-photos
|
||||||
|
* @desc Upload multiple foto veicolo (max 5)
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
router.post(
|
||||||
|
'/upload/vehicle-photos',
|
||||||
|
authenticate,
|
||||||
|
(req, res, next) => {
|
||||||
|
console.log('🚀 Request ricevuta per upload multiple foto veicolo');
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
uploadVehiclePhoto.array('photos', 5),
|
||||||
|
(err, req, res, next) => {
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Errore multer:', err);
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message || 'Errore durante upload',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
console.log('✅ Files ricevuti:', req.files);
|
||||||
|
|
||||||
|
if (!req.files || req.files.length === 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Nessuna foto caricata',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const photoUrls = req.files.map((file) => `/upload/vehicles/${file.filename}`);
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: `${req.files.length} foto caricate con successo`,
|
||||||
|
data: {
|
||||||
|
urls: photoUrls,
|
||||||
|
count: req.files.length,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Errore upload foto veicolo:', error);
|
||||||
|
|
||||||
|
// Elimina tutti i file in caso di errore
|
||||||
|
if (req.files && req.files.length > 0) {
|
||||||
|
req.files.forEach((file) => {
|
||||||
|
if (file.path) {
|
||||||
|
fs.unlinkSync(file.path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Errore durante l'upload delle foto",
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route POST /api/trasporti/upload/vehicle-photo
|
||||||
|
* @desc Upload foto veicolo
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
router.post(
|
||||||
|
'/upload/vehicle-photo',
|
||||||
|
authenticate,
|
||||||
|
(req, res, next) => {
|
||||||
|
console.log('🚀 Request ricevuta per upload foto veicolo');
|
||||||
|
console.log('📋 Headers:', req.headers);
|
||||||
|
console.log('📦 Body type:', typeof req.body);
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
uploadVehiclePhoto.single('photo'),
|
||||||
|
(err, req, res, next) => {
|
||||||
|
// Gestione errori multer
|
||||||
|
if (err) {
|
||||||
|
console.error('❌ Errore multer:', err);
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: err.message || 'Errore durante upload',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
},
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
console.log('✅ File ricevuto:', req.file);
|
||||||
|
console.log('📄 Body:', req.body);
|
||||||
|
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Nessuna foto caricata - req.file è undefined',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const photoUrl = `/upload/vehicles/${req.file.filename}`;
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Foto caricata con successo',
|
||||||
|
data: {
|
||||||
|
url: photoUrl,
|
||||||
|
filename: req.file.filename,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Errore upload foto veicolo:', error);
|
||||||
|
|
||||||
|
if (req.file && req.file.path) {
|
||||||
|
fs.unlinkSync(req.file.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Errore durante l'upload della foto",
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route DELETE /api/trasporti/upload/vehicle-photo
|
||||||
|
* @desc Elimina foto veicolo
|
||||||
|
* @access Private
|
||||||
|
*/
|
||||||
|
router.delete('/upload/vehicle-photo', authenticate, async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { photoUrl } = req.body;
|
||||||
|
|
||||||
|
if (!photoUrl) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
message: 'URL foto non fornito',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Estrai il filename dall'URL
|
||||||
|
const filename = photoUrl.split('/').pop();
|
||||||
|
const filepath = path.join(vehiclesDir, filename);
|
||||||
|
|
||||||
|
// Verifica che il file esista
|
||||||
|
if (fs.existsSync(filepath)) {
|
||||||
|
fs.unlinkSync(filepath);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: 'Foto eliminata con successo',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Errore eliminazione foto:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: "Errore durante l'eliminazione della foto",
|
||||||
|
error: error.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -709,7 +709,7 @@ class PosterRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Path locale
|
// Path locale
|
||||||
if (url.startsWith('/uploads') || url.startsWith('./uploads')) {
|
if (url.startsWith('/upload') || url.startsWith('./upload')) {
|
||||||
const localPath = url.startsWith('/')
|
const localPath = url.startsWith('/')
|
||||||
? path.join(process.cwd(), url)
|
? path.join(process.cwd(), url)
|
||||||
: url;
|
: url;
|
||||||
|
|||||||
Reference in New Issue
Block a user