- Parte 3 : Viaggi

- Chat
This commit is contained in:
Surya Paolo
2025-12-24 00:26:38 +01:00
parent b78e3ce544
commit cb965eaa27
20 changed files with 1752 additions and 793 deletions

View File

@@ -43,4 +43,5 @@ MIAB_ADMIN_EMAIL=admin@lamiaposta.org
MIAB_ADMIN_PASSWORD=passpao1pabox@1A
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
SERVER_A_URL="http://51.77.156.69:3000"
API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK"
API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK"
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -39,3 +39,4 @@ AUTH_NEW_SITES=123123123
SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -44,4 +44,5 @@ OLLAMA_DEFAULT_MODEL=llama3.2:3b
GROK_API="xai-PcNM5obgPaETtmnfDWPZk235D75ZgxENU2QmeqPfMQCHh9dwCDVeRRe0oVVA2YOpiUDh1uJieZsMasja"
REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u"
FAL_KEY="7d251c88-21b5-4b55-8b3e-4bafd910f99f:b81c0a36a25b052f26eb8ac226c7efff"
HF_TOKEN="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"
HF_TOKEN="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"
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -41,4 +41,5 @@ MIAB_HOST=box.lamiaposta.org
MIAB_ADMIN_EMAIL=admin@lamiaposta.org
MIAB_ADMIN_PASSWORD=passpao1pabox@1A
SERVER_A_URL="http://51.77.156.69:3000"
API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK"
API_KEY_MSSQL="m68yADSr123MIVIDA@154$DSAGVOK"
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -38,4 +38,5 @@ SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
MIAB_HOST=box.lamiaposta.org
MIAB_ADMIN_EMAIL=admin@lamiaposta.org
MIAB_ADMIN_PASSWORD=passpao1pabox@1A
MIAB_ADMIN_PASSWORD=passpao1pabox@1A
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -55,7 +55,11 @@ class UserController {
}
// Send response with tokens
res.header('x-auth', result.token).header('x-refrtok', result.refreshToken).header('x-browser-random', result.browser_random).send(result.user);
res
.header('x-auth', result.token)
.header('x-refrtok', result.refreshToken)
.header('x-browser-random', result.browser_random)
.send(result.user);
} catch (error) {
console.error('Error in registration:', error.message);
res.status(400).send({
@@ -103,11 +107,15 @@ class UserController {
}
// Send response with tokens
res.header('x-auth', result.token).header('x-refrtok', result.refreshToken).header('x-browser-random', result.browser_random).send({
usertosend: result.user,
code: server_constants.RIS_CODE_OK,
subsExistonDb: result.subsExistonDb,
});
res
.header('x-auth', result.token)
.header('x-refrtok', result.refreshToken)
.header('x-browser-random', result.browser_random)
.send({
usertosend: result.user,
code: server_constants.RIS_CODE_OK,
subsExistonDb: result.subsExistonDb,
});
} catch (error) {
console.error('Error in login:', error.message);
res.status(400).send({
@@ -487,6 +495,7 @@ class UserController {
const { User } = require('../models/user');
return User.isCollaboratore(user.perm);
}
}
module.exports = UserController;

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ const { User } = require('../models/user');
/**
* @desc Crea un feedback per un viaggio
* @route POST /api/trasporti/feedback
* @route POST /api/viaggi/feedback
* @access Private
*/
const createFeedback = async (req, res) => {
@@ -144,7 +144,7 @@ const createFeedback = async (req, res) => {
/**
* @desc Ottieni i feedback ricevuti da un utente
* @route GET /api/trasporti/feedback/user/:userId
* @route GET /api/viaggi/feedback/user/:userId
* @access Public
*/
const getUserFeedback = async (req, res) => {
@@ -206,13 +206,13 @@ const getUserFeedback = async (req, res) => {
/**
* @desc Ottieni statistiche feedback per un utente
* @route GET /api/trasporti/feedback/user/:userId/stats
* @route GET /api/viaggi/feedback/user/:userId/stats
* @access Public
*/
const getUserFeedbackStats = async (req, res) => {
try {
const { userId } = req.params;
const { idapp } = req.query;
const idapp = req.user.idapp;
if (!idapp) {
return res.status(400).json({
@@ -246,13 +246,13 @@ const getUserFeedbackStats = async (req, res) => {
/**
* @desc Ottieni i feedback per un viaggio
* @route GET /api/trasporti/feedback/ride/:rideId
* @route GET /api/viaggi/feedback/ride/:rideId
* @access Public (con info limitate) / Private (info complete)
*/
const getRideFeedback = async (req, res) => {
try {
const { rideId } = req.params;
const { idapp } = req.query;
const idapp = req.user.idapp;
const userId = req.user?._id;
if (!idapp) {
@@ -337,14 +337,14 @@ const getRideFeedback = async (req, res) => {
/**
* @desc Verifica se l'utente può lasciare un feedback
* @route GET /api/trasporti/feedback/can-leave/:rideId/:toUserId
* @route GET /api/viaggi/feedback/can-leave/:rideId/:toUserId
* @access Private
* @note NUOVA FUNZIONE - Era mancante!
*/
const canLeaveFeedback = async (req, res) => {
try {
const { rideId, toUserId } = req.params;
const { idapp } = req.query;
const idapp = req.user.idapp;
const fromUserId = req.user._id;
if (!idapp) {
@@ -476,7 +476,7 @@ const canLeaveFeedback = async (req, res) => {
/**
* @desc Rispondi a un feedback ricevuto
* @route POST /api/trasporti/feedback/:id/response
* @route POST /api/viaggi/feedback/:id/response
* @access Private
*/
const respondToFeedback = async (req, res) => {
@@ -542,7 +542,7 @@ const respondToFeedback = async (req, res) => {
/**
* @desc Segna un feedback come utile
* @route POST /api/trasporti/feedback/:id/helpful
* @route POST /api/viaggi/feedback/:id/helpful
* @access Private
*/
const markAsHelpful = async (req, res) => {
@@ -605,7 +605,7 @@ const markAsHelpful = async (req, res) => {
/**
* @desc Segnala un feedback inappropriato
* @route POST /api/trasporti/feedback/:id/report
* @route POST /api/viaggi/feedback/:id/report
* @access Private
*/
const reportFeedback = async (req, res) => {
@@ -679,7 +679,7 @@ const reportFeedback = async (req, res) => {
/**
* @desc Ottieni i miei feedback dati
* @route GET /api/trasporti/feedback/my/given
* @route GET /api/viaggi/feedback/my/given
* @access Private
*/
const getMyGivenFeedback = async (req, res) => {
@@ -729,7 +729,7 @@ const getMyGivenFeedback = async (req, res) => {
/**
* @desc Ottieni i miei feedback ricevuti
* @route GET /api/trasporti/feedback/my/received
* @route GET /api/viaggi/feedback/my/received
* @access Private
*/
const getMyReceivedFeedback = async (req, res) => {

View File

@@ -1,39 +1,43 @@
/**
* Controller per Geocoding usando servizi Open Source
* - Nominatim (OpenStreetMap) per geocoding/reverse
* - OSRM per routing
* - Photon per autocomplete
* Controller per Geocoding usando OpenRouteService
* Documentazione: https://openrouteservice.org/dev/#/api-docs
*/
const https = require('https');
const http = require('http');
// Configurazione servizi
const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org';
const PHOTON_BASE = 'https://photon.komoot.io';
const OSRM_BASE = 'https://router.project-osrm.org';
// User-Agent richiesto da Nominatim
const USER_AGENT = 'FreePlanetApp/1.0';
// Configurazione OpenRouteService
const ORS_BASE = 'https://api.openrouteservice.org';
const ORS_API_KEY = process.env.ORS_API_KEY || 'YOUR_API_KEY_HERE';
/**
* Helper per fare richieste HTTP/HTTPS
* Helper per fare richieste HTTPS a OpenRouteService
*/
const makeRequest = (url) => {
const makeRequest = (url, method = 'GET', body = null) => {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
const req = client.get(url, {
const urlObj = new URL(url);
const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method,
headers: {
'User-Agent': USER_AGENT,
'Accept': 'application/json'
}
}, (res) => {
Authorization: ORS_API_KEY,
Accept: 'application/json',
'Content-Type': 'application/json',
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
try {
resolve(JSON.parse(data));
const parsed = JSON.parse(data);
if (res.statusCode >= 400) {
reject(new Error(parsed.error?.message || `HTTP ${res.statusCode}`));
} else {
resolve(parsed);
}
} catch (e) {
reject(new Error('Errore parsing risposta'));
}
@@ -41,338 +45,436 @@ const makeRequest = (url) => {
});
req.on('error', reject);
req.setTimeout(10000, () => {
req.setTimeout(15000, () => {
req.destroy();
reject(new Error('Timeout richiesta'));
});
if (body) {
req.write(JSON.stringify(body));
}
req.end();
});
};
/**
* @desc Autocomplete città (Photon API)
* @route GET /api/trasporti/geo/autocomplete
* @desc Autocomplete città (ORS Geocode Autocomplete)
* @route GET /api/geo/autocomplete
*/
const autocomplete = async (req, res) => {
try {
const { q, limit = 5, lang = 'it' } = req.query;
const { q, limit = 5, lang = 'it', country = 'IT' } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({
success: false,
message: 'Query deve essere almeno 2 caratteri'
message: 'Query deve essere almeno 2 caratteri',
});
}
// Photon API - gratuito e veloce
const url = `${PHOTON_BASE}/api/?q=${encodeURIComponent(q)}&limit=${limit}&lang=${lang}&osm_tag=place:city&osm_tag=place:town&osm_tag=place:village`;
const params = new URLSearchParams({
text: q,
size: limit,
lang,
'boundary.country': country,
layers: 'locality,county,region', // Solo città/comuni
});
const url = `${ORS_BASE}/geocode/autocomplete?${params}`;
const data = await makeRequest(url);
// Formatta risultati
const results = data.features.map(feature => ({
const results = data.features.map((feature) => ({
id: feature.properties.id,
city: feature.properties.name,
province: feature.properties.county || feature.properties.state,
region: feature.properties.state,
locality: feature.properties.locality,
county: feature.properties.county,
region: feature.properties.region,
country: feature.properties.country,
postalCode: feature.properties.postcode,
postalCode: feature.properties.postalcode,
coordinates: {
lat: feature.geometry.coordinates[1],
lng: feature.geometry.coordinates[0]
lng: feature.geometry.coordinates[0],
},
displayName: [
feature.properties.name,
feature.properties.county,
feature.properties.state,
feature.properties.country
].filter(Boolean).join(', '),
type: feature.properties.osm_value || 'place'
displayName: feature.properties.label,
type: feature.properties.layer,
confidence: feature.properties.confidence,
}));
res.status(200).json({
success: true,
data: results
count: results.length,
data: results,
});
} catch (error) {
console.error('Errore autocomplete:', error);
res.status(500).json({
success: false,
message: 'Errore durante la ricerca',
error: error.message
error: error.message,
});
}
};
/**
* @desc Geocoding - indirizzo a coordinate (Nominatim)
* @route GET /api/trasporti/geo/geocode
* @desc Geocoding - indirizzo a coordinate (ORS Geocode Search)
* @route GET /api/geo/geocode
*/
const geocode = async (req, res) => {
try {
const { address, city, country = 'Italy' } = req.query;
const { address, city, country = 'IT', limit = 5, lang = 'it' } = req.query;
const searchQuery = [address, city, country].filter(Boolean).join(', ');
const searchQuery = [address, city].filter(Boolean).join(', ');
if (!searchQuery) {
return res.status(400).json({
success: false,
message: 'Fornisci un indirizzo o città da cercare'
message: 'Fornisci un indirizzo o città da cercare',
});
}
const url = `${NOMINATIM_BASE}/search?format=json&q=${encodeURIComponent(searchQuery)}&limit=5&addressdetails=1`;
const params = new URLSearchParams({
text: searchQuery,
size: limit,
lang,
'boundary.country': country,
});
const url = `${ORS_BASE}/geocode/search?${params}`;
const data = await makeRequest(url);
if (!data || data.length === 0) {
if (!data.features || data.features.length === 0) {
return res.status(404).json({
success: false,
message: 'Nessun risultato trovato'
message: 'Nessun risultato trovato',
});
}
const results = data.map(item => ({
displayName: item.display_name,
city: item.address.city || item.address.town || item.address.village || item.address.municipality,
address: item.address.road ? `${item.address.road}${item.address.house_number ? ' ' + item.address.house_number : ''}` : null,
province: item.address.county || item.address.province,
region: item.address.state,
country: item.address.country,
postalCode: item.address.postcode,
const results = data.features.map((feature) => ({
id: feature.properties.id,
displayName: feature.properties.label,
name: feature.properties.name,
street: feature.properties.street,
houseNumber: feature.properties.housenumber,
city: feature.properties.locality || feature.properties.county,
county: feature.properties.county,
region: feature.properties.region,
country: feature.properties.country,
postalCode: feature.properties.postalcode,
coordinates: {
lat: parseFloat(item.lat),
lng: parseFloat(item.lon)
lat: feature.geometry.coordinates[1],
lng: feature.geometry.coordinates[0],
},
type: item.type,
importance: item.importance
type: feature.properties.layer,
confidence: feature.properties.confidence,
}));
res.status(200).json({
success: true,
data: results
count: results.length,
data: results,
});
} catch (error) {
console.error('Errore geocoding:', error);
res.status(500).json({
success: false,
message: 'Errore durante il geocoding',
error: error.message
error: error.message,
});
}
};
/**
* @desc Reverse geocoding - coordinate a indirizzo (Nominatim)
* @route GET /api/trasporti/geo/reverse
* @desc Reverse geocoding - coordinate a indirizzo (ORS Reverse)
* @route GET /api/geo/reverse
*/
const reverseGeocode = async (req, res) => {
try {
const { lat, lng } = req.query;
const { lat, lng, lang = 'it' } = req.query;
if (!lat || !lng) {
return res.status(400).json({
success: false,
message: 'Coordinate lat e lng richieste'
message: 'Coordinate lat e lng richieste',
});
}
const url = `${NOMINATIM_BASE}/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1`;
const params = new URLSearchParams({
'point.lat': lat,
'point.lon': lng,
lang,
size: '1',
layers: 'address,street,locality',
});
const url = `${ORS_BASE}/geocode/reverse?${params}`;
const data = await makeRequest(url);
if (!data || data.error) {
if (!data.features || data.features.length === 0) {
return res.status(404).json({
success: false,
message: 'Nessun risultato trovato'
message: 'Nessun risultato trovato',
});
}
const feature = data.features[0];
const result = {
displayName: data.display_name,
city: data.address.city || data.address.town || data.address.village || data.address.municipality,
address: data.address.road ? `${data.address.road}${data.address.house_number ? ' ' + data.address.house_number : ''}` : null,
province: data.address.county || data.address.province,
region: data.address.state,
country: data.address.country,
postalCode: data.address.postcode,
displayName: feature.properties.label,
name: feature.properties.name,
street: feature.properties.street,
houseNumber: feature.properties.housenumber,
city: feature.properties.locality || feature.properties.county,
county: feature.properties.county,
region: feature.properties.region,
country: feature.properties.country,
postalCode: feature.properties.postalcode,
coordinates: {
lat: parseFloat(lat),
lng: parseFloat(lng)
}
lng: parseFloat(lng),
},
distance: feature.properties.distance, // distanza dal punto esatto
};
res.status(200).json({
success: true,
data: result
data: result,
});
} catch (error) {
console.error('Errore reverse geocoding:', error);
res.status(500).json({
success: false,
message: 'Errore durante il reverse geocoding',
error: error.message
error: error.message,
});
}
};
/**
* @desc Calcola percorso tra due punti (OSRM)
* @route GET /api/trasporti/geo/route
* @desc Calcola percorso tra due o più punti (ORS Directions)
* @route POST /api/geo/route
* @body { coordinates: [[lng,lat], [lng,lat], ...], profile: 'driving-car' }
*/
const getRoute = async (req, res) => {
try {
const {
startLat, startLng,
endLat, endLng,
waypoints // formato: "lat1,lng1;lat2,lng2;..."
const {
startLat,
startLng,
endLat,
endLng,
waypoints, // formato: "lat1,lng1;lat2,lng2;..."
profile = 'driving-car', // driving-car, driving-hgv, cycling-regular, foot-walking
language = 'it',
units = 'km',
} = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Coordinate di partenza e arrivo richieste'
message: 'Coordinate di partenza e arrivo richieste',
});
}
// Costruisci stringa coordinate
let coordinates = `${startLng},${startLat}`;
// Costruisci array coordinate [lng, lat] (formato GeoJSON)
const coordinates = [[parseFloat(startLng), parseFloat(startLat)]];
if (waypoints) {
const waypointsList = waypoints.split(';');
waypointsList.forEach(wp => {
const [lat, lng] = wp.split(',');
coordinates += `;${lng},${lat}`;
waypointsList.forEach((wp) => {
const [lat, lng] = wp.split(',').map(parseFloat);
coordinates.push([lng, lat]);
});
}
coordinates += `;${endLng},${endLat}`;
const url = `${OSRM_BASE}/route/v1/driving/${coordinates}?overview=full&geometries=polyline&steps=true`;
coordinates.push([parseFloat(endLng), parseFloat(endLat)]);
const data = await makeRequest(url);
// Richiesta POST a ORS Directions
const url = `${ORS_BASE}/v2/directions/${profile}`;
if (!data || data.code !== 'Ok' || !data.routes || data.routes.length === 0) {
const body = {
coordinates,
language,
units,
geometry: true,
instructions: true,
maneuvers: true,
};
const data = await makeRequest(url, 'POST', body);
if (!data.routes || data.routes.length === 0) {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare il percorso'
message: 'Impossibile calcolare il percorso',
});
}
const route = data.routes[0];
// Estrai città attraversate (dalle istruzioni)
const citiesAlongRoute = [];
if (route.legs) {
route.legs.forEach(leg => {
if (leg.steps) {
leg.steps.forEach(step => {
if (step.name && step.name.length > 0) {
// Qui potresti fare reverse geocoding per ottenere città
// Per ora usiamo i nomi delle strade principali
}
});
}
});
}
const summary = route.summary;
const result = {
distance: Math.round(route.distance / 1000 * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti
polyline: route.geometry, // Polyline encoded
legs: route.legs.map(leg => ({
distance: Math.round(leg.distance / 1000 * 10) / 10,
duration: Math.round(leg.duration / 60),
summary: leg.summary,
steps: leg.steps ? leg.steps.slice(0, 10).map(s => ({ // Limita step
instruction: s.maneuver ? s.maneuver.instruction : '',
name: s.name,
distance: Math.round(s.distance),
duration: Math.round(s.duration / 60)
})) : []
}))
distance: Math.round(summary.distance * 10) / 10, // km
duration: Math.round(summary.duration / 60), // minuti
durationFormatted: formatDuration(summary.duration),
bbox: data.bbox, // Bounding box
geometry: route.geometry, // Polyline encoded
segments: route.segments.map((segment) => ({
distance: Math.round(segment.distance * 10) / 10,
duration: Math.round(segment.duration / 60),
steps: segment.steps.map((step) => ({
instruction: step.instruction,
name: step.name,
distance: Math.round(step.distance * 100) / 100,
duration: Math.round(step.duration / 60),
type: step.type,
maneuver: step.maneuver,
})),
})),
};
res.status(200).json({
success: true,
data: result
data: result,
});
} catch (error) {
console.error('Errore calcolo percorso:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo del percorso',
error: error.message
error: error.message,
});
}
};
/**
* @desc Calcola matrice distanze tra più punti (ORS Matrix)
* @route POST /api/geo/matrix
*/
const getMatrix = async (req, res) => {
try {
const { locations, profile = 'driving-car' } = req.body;
if (!locations || locations.length < 2) {
return res.status(400).json({
success: false,
message: 'Almeno 2 location richieste',
});
}
// Formato locations: [[lng, lat], [lng, lat], ...]
const url = `${ORS_BASE}/v2/matrix/${profile}`;
const body = {
locations,
metrics: ['distance', 'duration'],
units: 'km',
};
const data = await makeRequest(url, 'POST', body);
const result = {
distances: data.distances, // Matrice distanze in km
durations: data.durations, // Matrice durate in secondi
sources: data.sources,
destinations: data.destinations,
};
res.status(200).json({
success: true,
data: result,
});
} catch (error) {
console.error('Errore calcolo matrice:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo della matrice',
error: error.message,
});
}
};
/**
* @desc Suggerisci città intermedie su un percorso
* @route GET /api/trasporti/geo/suggest-waypoints
* @route GET /api/geo/suggest-waypoints
*/
const suggestWaypoints = async (req, res) => {
try {
const { startLat, startLng, endLat, endLng } = req.query;
const { startLat, startLng, endLat, endLng, count = 3 } = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Coordinate di partenza e arrivo richieste'
message: 'Coordinate di partenza e arrivo richieste',
});
}
// Prima ottieni il percorso
const routeUrl = `${OSRM_BASE}/route/v1/driving/${startLng},${startLat};${endLng},${endLat}?overview=full&geometries=geojson`;
const routeUrl = `${ORS_BASE}/v2/directions/driving-car`;
const routeBody = {
coordinates: [
[parseFloat(startLng), parseFloat(startLat)],
[parseFloat(endLng), parseFloat(endLat)],
],
geometry: true,
};
const routeData = await makeRequest(routeUrl);
const routeData = await makeRequest(routeUrl, 'POST', routeBody);
if (!routeData || routeData.code !== 'Ok') {
if (!routeData.routes || routeData.routes.length === 0) {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare il percorso'
message: 'Impossibile calcolare il percorso',
});
}
// Prendi punti lungo il percorso (ogni ~50km circa)
const coordinates = routeData.routes[0].geometry.coordinates;
const totalPoints = coordinates.length;
const step = Math.max(1, Math.floor(totalPoints / 6)); // ~5 punti intermedi
// Decodifica polyline per ottenere punti
const geometry = routeData.routes[0].geometry;
const decodedPoints = decodePolyline(geometry);
// Seleziona punti equidistanti lungo il percorso
const totalPoints = decodedPoints.length;
const step = Math.floor(totalPoints / (parseInt(count) + 1));
const sampledPoints = [];
for (let i = step; i < totalPoints - step; i += step) {
sampledPoints.push(coordinates[i]);
for (let i = 1; i <= count; i++) {
const index = Math.min(step * i, totalPoints - 1);
sampledPoints.push(decodedPoints[index]);
}
// Fai reverse geocoding per ogni punto
const cities = [];
const seenCities = new Set();
for (const point of sampledPoints.slice(0, 5)) { // Limita a 5 richieste
for (const point of sampledPoints) {
try {
const reverseUrl = `${NOMINATIM_BASE}/reverse?format=json&lat=${point[1]}&lon=${point[0]}&addressdetails=1&zoom=10`;
const params = new URLSearchParams({
'point.lat': point[1],
'point.lon': point[0],
lang: 'it',
size: '1',
layers: 'locality,county',
});
const reverseUrl = `${ORS_BASE}/geocode/reverse?${params}`;
const data = await makeRequest(reverseUrl);
if (data && data.address) {
const cityName = data.address.city || data.address.town || data.address.village;
if (data.features && data.features.length > 0) {
const feature = data.features[0];
const cityName = feature.properties.locality || feature.properties.county;
if (cityName && !seenCities.has(cityName.toLowerCase())) {
seenCities.add(cityName.toLowerCase());
cities.push({
city: cityName,
province: data.address.county || data.address.province,
region: data.address.state,
county: feature.properties.county,
region: feature.properties.region,
coordinates: {
lat: point[1],
lng: point[0]
}
lng: point[0],
},
displayName: feature.properties.label,
});
}
}
// Rate limiting - aspetta 1 secondo tra le richieste (requisito Nominatim)
await new Promise(resolve => setTimeout(resolve, 1100));
} catch (e) {
console.log('Errore reverse per punto:', e.message);
}
@@ -380,128 +482,196 @@ const suggestWaypoints = async (req, res) => {
res.status(200).json({
success: true,
data: cities
count: cities.length,
data: cities,
});
} catch (error) {
console.error('Errore suggerimento waypoints:', error);
res.status(500).json({
success: false,
message: 'Errore durante il suggerimento delle tappe',
error: error.message
error: error.message,
});
}
};
/**
* @desc Cerca città italiane (ottimizzato per Italia)
* @route GET /api/trasporti/geo/cities/it
* @desc Cerca città italiane (ottimizzato)
* @route GET /api/geo/cities/it
*/
const searchItalianCities = async (req, res) => {
try {
const { q, limit = 10 } = req.query;
const { q, limit = 10, region } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({
success: false,
message: 'Query deve essere almeno 2 caratteri'
message: 'Query deve essere almeno 2 caratteri',
});
}
// Usa Nominatim con filtro Italia
const url = `${NOMINATIM_BASE}/search?format=json&q=${encodeURIComponent(q)}&countrycodes=it&limit=${limit}&addressdetails=1&featuretype=city`;
const params = new URLSearchParams({
text: q,
size: limit,
lang: 'it',
'boundary.country': 'IT',
layers: 'locality,county',
});
// Filtro opzionale per regione
if (region) {
params.append('region', region);
}
const url = `${ORS_BASE}/geocode/search?${params}`;
const data = await makeRequest(url);
const results = data
.filter(item =>
item.address &&
(item.address.city || item.address.town || item.address.village)
)
.map(item => ({
city: item.address.city || item.address.town || item.address.village,
province: item.address.county || item.address.province,
region: item.address.state,
postalCode: item.address.postcode,
const results = data.features
.filter((f) => f.properties.locality || f.properties.county)
.map((feature) => ({
city: feature.properties.locality || feature.properties.name,
county: feature.properties.county,
region: feature.properties.region,
postalCode: feature.properties.postalcode,
coordinates: {
lat: parseFloat(item.lat),
lng: parseFloat(item.lon)
lat: feature.geometry.coordinates[1],
lng: feature.geometry.coordinates[0],
},
displayName: `${item.address.city || item.address.town || item.address.village}, ${item.address.county || item.address.state}`
displayName: `${feature.properties.locality || feature.properties.name}, ${feature.properties.region}`,
confidence: feature.properties.confidence,
}));
// Rimuovi duplicati
const unique = results.filter((v, i, a) =>
a.findIndex(t => t.city.toLowerCase() === v.city.toLowerCase()) === i
const unique = results.filter(
(v, i, a) => a.findIndex((t) => t.city?.toLowerCase() === v.city?.toLowerCase()) === i
);
res.status(200).json({
success: true,
data: unique
count: unique.length,
data: unique,
});
} catch (error) {
console.error('Errore ricerca città italiane:', error);
res.status(500).json({
success: false,
message: 'Errore durante la ricerca',
error: error.message
error: error.message,
});
}
};
/**
* @desc Calcola distanza e durata tra due punti
* @route GET /api/trasporti/geo/distance
* @desc Calcola distanza e durata tra due punti (semplificato)
* @route GET /api/geo/distance
*/
const getDistance = async (req, res) => {
try {
const { startLat, startLng, endLat, endLng } = req.query;
const { startLat, startLng, endLat, endLng, profile = 'driving-car' } = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Tutte le coordinate sono richieste'
message: 'Tutte le coordinate sono richieste',
});
}
const url = `${OSRM_BASE}/route/v1/driving/${startLng},${startLat};${endLng},${endLat}?overview=false`;
const url = `${ORS_BASE}/v2/directions/${profile}`;
const data = await makeRequest(url);
const body = {
coordinates: [
[parseFloat(startLng), parseFloat(startLat)],
[parseFloat(endLng), parseFloat(endLat)],
],
geometry: false,
instructions: false,
};
if (!data || data.code !== 'Ok' || !data.routes || data.routes.length === 0) {
const data = await makeRequest(url, 'POST', body);
if (!data.routes || data.routes.length === 0) {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare la distanza'
message: 'Impossibile calcolare la distanza',
});
}
const route = data.routes[0];
const summary = data.routes[0].summary;
res.status(200).json({
success: true,
data: {
distance: Math.round(route.distance / 1000 * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti
durationFormatted: formatDuration(route.duration)
}
distance: Math.round(summary.distance * 10) / 10, // km
duration: Math.round(summary.duration / 60), // minuti
durationFormatted: formatDuration(summary.duration),
profile,
},
});
} catch (error) {
console.error('Errore calcolo distanza:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo della distanza',
error: error.message
error: error.message,
});
}
};
// Helper per formattare durata
/**
* @desc Ottieni isocrone (aree raggiungibili in X minuti)
* @route GET /api/geo/isochrone
*/
const getIsochrone = async (req, res) => {
try {
const { lat, lng, minutes = 30, profile = 'driving-car' } = req.query;
if (!lat || !lng) {
return res.status(400).json({
success: false,
message: 'Coordinate richieste',
});
}
const url = `${ORS_BASE}/v2/isochrones/${profile}`;
const body = {
locations: [[parseFloat(lng), parseFloat(lat)]],
range: [parseInt(minutes) * 60], // secondi
range_type: 'time',
};
const data = await makeRequest(url, 'POST', body);
res.status(200).json({
success: true,
data: {
type: 'FeatureCollection',
features: data.features,
center: { lat: parseFloat(lat), lng: parseFloat(lng) },
minutes: parseInt(minutes),
},
});
} catch (error) {
console.error('Errore calcolo isocrone:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo isocrone',
error: error.message,
});
}
};
// ============================================
// HELPER FUNCTIONS
// ============================================
/**
* Formatta durata in formato leggibile
*/
const formatDuration = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.round((seconds % 3600) / 60);
if (hours === 0) {
return `${minutes} min`;
} else if (minutes === 0) {
@@ -511,12 +681,55 @@ const formatDuration = (seconds) => {
}
};
/**
* Decodifica polyline encoded (formato Google/ORS)
*/
const decodePolyline = (encoded) => {
const points = [];
let index = 0;
let lat = 0;
let lng = 0;
while (index < encoded.length) {
let b;
let shift = 0;
let result = 0;
do {
b = encoded.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
const dlat = result & 1 ? ~(result >> 1) : result >> 1;
lat += dlat;
shift = 0;
result = 0;
do {
b = encoded.charCodeAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
const dlng = result & 1 ? ~(result >> 1) : result >> 1;
lng += dlng;
points.push([lng / 1e5, lat / 1e5]); // [lng, lat] formato GeoJSON
}
return points;
};
module.exports = {
autocomplete,
geocode,
reverseGeocode,
getRoute,
getMatrix,
suggestWaypoints,
searchItalianCities,
getDistance
};
getDistance,
getIsochrone,
};

View File

@@ -0,0 +1,522 @@
/**
* Controller per Geocoding usando servizi Open Source
* - Nominatim (OpenStreetMap) per geocoding/reverse
* - OSRM per routing
* - Photon per autocomplete
*/
const https = require('https');
const http = require('http');
// Configurazione servizi
const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org';
const PHOTON_BASE = 'https://photon.komoot.io';
const OSRM_BASE = 'https://router.project-osrm.org';
// User-Agent richiesto da Nominatim
const USER_AGENT = 'FreePlanetApp/1.0';
/**
* Helper per fare richieste HTTP/HTTPS
*/
const makeRequest = (url) => {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
const req = client.get(url, {
headers: {
'User-Agent': USER_AGENT,
'Accept': 'application/json'
}
}, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
reject(new Error('Errore parsing risposta'));
}
});
});
req.on('error', reject);
req.setTimeout(10000, () => {
req.destroy();
reject(new Error('Timeout richiesta'));
});
});
};
/**
* @desc Autocomplete città (Photon API)
* @route GET /api/viaggi/geo/autocomplete
*/
const autocomplete = async (req, res) => {
try {
const { q, limit = 5, lang = 'it' } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({
success: false,
message: 'Query deve essere almeno 2 caratteri'
});
}
// Photon API - gratuito e veloce
const url = `${PHOTON_BASE}/api/?q=${encodeURIComponent(q)}&limit=${limit}&lang=${lang}&osm_tag=place:city&osm_tag=place:town&osm_tag=place:village`;
const data = await makeRequest(url);
// Formatta risultati
const results = data.features.map(feature => ({
city: feature.properties.name,
province: feature.properties.county || feature.properties.state,
region: feature.properties.state,
country: feature.properties.country,
postalCode: feature.properties.postcode,
coordinates: {
lat: feature.geometry.coordinates[1],
lng: feature.geometry.coordinates[0]
},
displayName: [
feature.properties.name,
feature.properties.county,
feature.properties.state,
feature.properties.country
].filter(Boolean).join(', '),
type: feature.properties.osm_value || 'place'
}));
res.status(200).json({
success: true,
data: results
});
} catch (error) {
console.error('Errore autocomplete:', error);
res.status(500).json({
success: false,
message: 'Errore durante la ricerca',
error: error.message
});
}
};
/**
* @desc Geocoding - indirizzo a coordinate (Nominatim)
* @route GET /api/viaggi/geo/geocode
*/
const geocode = async (req, res) => {
try {
const { address, city, country = 'Italy' } = req.query;
const searchQuery = [address, city, country].filter(Boolean).join(', ');
if (!searchQuery) {
return res.status(400).json({
success: false,
message: 'Fornisci un indirizzo o città da cercare'
});
}
const url = `${NOMINATIM_BASE}/search?format=json&q=${encodeURIComponent(searchQuery)}&limit=5&addressdetails=1`;
const data = await makeRequest(url);
if (!data || data.length === 0) {
return res.status(404).json({
success: false,
message: 'Nessun risultato trovato'
});
}
const results = data.map(item => ({
displayName: item.display_name,
city: item.address.city || item.address.town || item.address.village || item.address.municipality,
address: item.address.road ? `${item.address.road}${item.address.house_number ? ' ' + item.address.house_number : ''}` : null,
province: item.address.county || item.address.province,
region: item.address.state,
country: item.address.country,
postalCode: item.address.postcode,
coordinates: {
lat: parseFloat(item.lat),
lng: parseFloat(item.lon)
},
type: item.type,
importance: item.importance
}));
res.status(200).json({
success: true,
data: results
});
} catch (error) {
console.error('Errore geocoding:', error);
res.status(500).json({
success: false,
message: 'Errore durante il geocoding',
error: error.message
});
}
};
/**
* @desc Reverse geocoding - coordinate a indirizzo (Nominatim)
* @route GET /api/viaggi/geo/reverse
*/
const reverseGeocode = async (req, res) => {
try {
const { lat, lng } = req.query;
if (!lat || !lng) {
return res.status(400).json({
success: false,
message: 'Coordinate lat e lng richieste'
});
}
const url = `${NOMINATIM_BASE}/reverse?format=json&lat=${lat}&lon=${lng}&addressdetails=1`;
const data = await makeRequest(url);
if (!data || data.error) {
return res.status(404).json({
success: false,
message: 'Nessun risultato trovato'
});
}
const result = {
displayName: data.display_name,
city: data.address.city || data.address.town || data.address.village || data.address.municipality,
address: data.address.road ? `${data.address.road}${data.address.house_number ? ' ' + data.address.house_number : ''}` : null,
province: data.address.county || data.address.province,
region: data.address.state,
country: data.address.country,
postalCode: data.address.postcode,
coordinates: {
lat: parseFloat(lat),
lng: parseFloat(lng)
}
};
res.status(200).json({
success: true,
data: result
});
} catch (error) {
console.error('Errore reverse geocoding:', error);
res.status(500).json({
success: false,
message: 'Errore durante il reverse geocoding',
error: error.message
});
}
};
/**
* @desc Calcola percorso tra due punti (OSRM)
* @route GET /api/viaggi/geo/route
*/
const getRoute = async (req, res) => {
try {
const {
startLat, startLng,
endLat, endLng,
waypoints // formato: "lat1,lng1;lat2,lng2;..."
} = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Coordinate di partenza e arrivo richieste'
});
}
// Costruisci stringa coordinate
let coordinates = `${startLng},${startLat}`;
if (waypoints) {
const waypointsList = waypoints.split(';');
waypointsList.forEach(wp => {
const [lat, lng] = wp.split(',');
coordinates += `;${lng},${lat}`;
});
}
coordinates += `;${endLng},${endLat}`;
const url = `${OSRM_BASE}/route/v1/driving/${coordinates}?overview=full&geometries=polyline&steps=true`;
const data = await makeRequest(url);
if (!data || data.code !== 'Ok' || !data.routes || data.routes.length === 0) {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare il percorso'
});
}
const route = data.routes[0];
// Estrai città attraversate (dalle istruzioni)
const citiesAlongRoute = [];
if (route.legs) {
route.legs.forEach(leg => {
if (leg.steps) {
leg.steps.forEach(step => {
if (step.name && step.name.length > 0) {
// Qui potresti fare reverse geocoding per ottenere città
// Per ora usiamo i nomi delle strade principali
}
});
}
});
}
const result = {
distance: Math.round(route.distance / 1000 * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti
polyline: route.geometry, // Polyline encoded
legs: route.legs.map(leg => ({
distance: Math.round(leg.distance / 1000 * 10) / 10,
duration: Math.round(leg.duration / 60),
summary: leg.summary,
steps: leg.steps ? leg.steps.slice(0, 10).map(s => ({ // Limita step
instruction: s.maneuver ? s.maneuver.instruction : '',
name: s.name,
distance: Math.round(s.distance),
duration: Math.round(s.duration / 60)
})) : []
}))
};
res.status(200).json({
success: true,
data: result
});
} catch (error) {
console.error('Errore calcolo percorso:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo del percorso',
error: error.message
});
}
};
/**
* @desc Suggerisci città intermedie su un percorso
* @route GET /api/viaggi/geo/suggest-waypoints
*/
const suggestWaypoints = async (req, res) => {
try {
const { startLat, startLng, endLat, endLng } = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Coordinate di partenza e arrivo richieste'
});
}
// Prima ottieni il percorso
const routeUrl = `${OSRM_BASE}/route/v1/driving/${startLng},${startLat};${endLng},${endLat}?overview=full&geometries=geojson`;
const routeData = await makeRequest(routeUrl);
if (!routeData || routeData.code !== 'Ok') {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare il percorso'
});
}
// Prendi punti lungo il percorso (ogni ~50km circa)
const coordinates = routeData.routes[0].geometry.coordinates;
const totalPoints = coordinates.length;
const step = Math.max(1, Math.floor(totalPoints / 6)); // ~5 punti intermedi
const sampledPoints = [];
for (let i = step; i < totalPoints - step; i += step) {
sampledPoints.push(coordinates[i]);
}
// Fai reverse geocoding per ogni punto
const cities = [];
const seenCities = new Set();
for (const point of sampledPoints.slice(0, 5)) { // Limita a 5 richieste
try {
const reverseUrl = `${NOMINATIM_BASE}/reverse?format=json&lat=${point[1]}&lon=${point[0]}&addressdetails=1&zoom=10`;
const data = await makeRequest(reverseUrl);
if (data && data.address) {
const cityName = data.address.city || data.address.town || data.address.village;
if (cityName && !seenCities.has(cityName.toLowerCase())) {
seenCities.add(cityName.toLowerCase());
cities.push({
city: cityName,
province: data.address.county || data.address.province,
region: data.address.state,
coordinates: {
lat: point[1],
lng: point[0]
}
});
}
}
// Rate limiting - aspetta 1 secondo tra le richieste (requisito Nominatim)
await new Promise(resolve => setTimeout(resolve, 1100));
} catch (e) {
console.log('Errore reverse per punto:', e.message);
}
}
res.status(200).json({
success: true,
data: cities
});
} catch (error) {
console.error('Errore suggerimento waypoints:', error);
res.status(500).json({
success: false,
message: 'Errore durante il suggerimento delle tappe',
error: error.message
});
}
};
/**
* @desc Cerca città italiane (ottimizzato per Italia)
* @route GET /api/viaggi/geo/cities/it
*/
const searchItalianCities = async (req, res) => {
try {
const { q, limit = 10 } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({
success: false,
message: 'Query deve essere almeno 2 caratteri'
});
}
// Usa Nominatim con filtro Italia
const url = `${NOMINATIM_BASE}/search?format=json&q=${encodeURIComponent(q)}&countrycodes=it&limit=${limit}&addressdetails=1&featuretype=city`;
const data = await makeRequest(url);
const results = data
.filter(item =>
item.address &&
(item.address.city || item.address.town || item.address.village)
)
.map(item => ({
city: item.address.city || item.address.town || item.address.village,
province: item.address.county || item.address.province,
region: item.address.state,
postalCode: item.address.postcode,
coordinates: {
lat: parseFloat(item.lat),
lng: parseFloat(item.lon)
},
displayName: `${item.address.city || item.address.town || item.address.village}, ${item.address.county || item.address.state}`
}));
// Rimuovi duplicati
const unique = results.filter((v, i, a) =>
a.findIndex(t => t.city.toLowerCase() === v.city.toLowerCase()) === i
);
res.status(200).json({
success: true,
data: unique
});
} catch (error) {
console.error('Errore ricerca città italiane:', error);
res.status(500).json({
success: false,
message: 'Errore durante la ricerca',
error: error.message
});
}
};
/**
* @desc Calcola distanza e durata tra due punti
* @route GET /api/viaggi/geo/distance
*/
const getDistance = async (req, res) => {
try {
const { startLat, startLng, endLat, endLng } = req.query;
if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({
success: false,
message: 'Tutte le coordinate sono richieste'
});
}
const url = `${OSRM_BASE}/route/v1/driving/${startLng},${startLat};${endLng},${endLat}?overview=false`;
const data = await makeRequest(url);
if (!data || data.code !== 'Ok' || !data.routes || data.routes.length === 0) {
return res.status(404).json({
success: false,
message: 'Impossibile calcolare la distanza'
});
}
const route = data.routes[0];
res.status(200).json({
success: true,
data: {
distance: Math.round(route.distance / 1000 * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti
durationFormatted: formatDuration(route.duration)
}
});
} catch (error) {
console.error('Errore calcolo distanza:', error);
res.status(500).json({
success: false,
message: 'Errore durante il calcolo della distanza',
error: error.message
});
}
};
// Helper per formattare durata
const formatDuration = (seconds) => {
const hours = Math.floor(seconds / 3600);
const minutes = Math.round((seconds % 3600) / 60);
if (hours === 0) {
return `${minutes} min`;
} else if (minutes === 0) {
return `${hours} h`;
} else {
return `${hours} h ${minutes} min`;
}
};
module.exports = {
autocomplete,
geocode,
reverseGeocode,
getRoute,
suggestWaypoints,
searchItalianCities,
getDistance
};

View File

@@ -1,15 +1,15 @@
const Ride = require('../models/Ride');
const User = require('../models/user');
const { User } = require('../models/user');
const RideRequest = require('../models/RideRequest');
/**
* @desc Crea un nuovo viaggio (offerta o richiesta)
* @route POST /api/trasporti/rides
* @route POST /api/viaggi/rides
* @access Private
*/
const createRide = async (req, res) => {
try {
const { idapp } = req.body;
const idapp = req.user.idapp;
const userId = req.user._id;
const {
@@ -111,9 +111,7 @@ const createRide = async (req, res) => {
// Aggiorna profilo utente come driver se è un'offerta
if (type === 'offer') {
await User.findByIdAndUpdate(userId, {
'profile.driverProfile.isDriver': true
});
await User.findByIdAndUpdate(userId, { $set: { 'profile.driverProfile.isDriver': true } }, { new: true });
}
// Popola i dati per la risposta
@@ -137,12 +135,12 @@ const createRide = async (req, res) => {
/**
* @desc Ottieni lista viaggi con filtri
* @route GET /api/trasporti/rides
* @route GET /api/viaggi/rides
* @access Public
*/
const getRides = async (req, res) => {
try {
const { idapp } = req.query;
const idapp = req.query.idapp;
const {
type,
@@ -272,15 +270,14 @@ const getRides = async (req, res) => {
/**
* @desc Ottieni singolo viaggio per ID
* @route GET /api/trasporti/rides/:id
* @route GET /api/viaggi/rides/:id
* @access Public
*/
const getRideById = async (req, res) => {
try {
const { id } = req.params;
const { idapp } = req.query;
const ride = await Ride.findOne({ _id: id, idapp })
const ride = await Ride.findOne({ _id: id })
.populate('userId', 'username name surname profile.img profile.Biografia profile.driverProfile')
.populate('confirmedPassengers.userId', 'username name surname profile.img')
.populate('contribution.contribTypes.contribTypeId');
@@ -313,14 +310,15 @@ const getRideById = async (req, res) => {
/**
* @desc Aggiorna un viaggio
* @route PUT /api/trasporti/rides/:id
* @route PUT /api/viaggi/rides/:id
* @access Private
*/
const updateRide = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user._id;
const { idapp, ...updateData } = req.body;
const idapp = req.user.idapp;
const { ...updateData } = req.body;
// Trova il viaggio
const ride = await Ride.findOne({ _id: id, idapp });
@@ -333,7 +331,7 @@ const updateRide = async (req, res) => {
}
// Verifica proprietario
if (ride.userId.toString() !== userId) {
if (!ride.userId.equals(userId)) {
return res.status(403).json({
success: false,
message: 'Non sei autorizzato a modificare questo viaggio'
@@ -400,14 +398,15 @@ const updateRide = async (req, res) => {
/**
* @desc Cancella un viaggio
* @route DELETE /api/trasporti/rides/:id
* @route DELETE /api/viaggi/rides/:id
* @access Private
*/
const deleteRide = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user._id;
const { idapp, reason } = req.body;
const idapp = req.user.idapp;
const { reason } = req.body;
const ride = await Ride.findOne({ _id: id, idapp });
@@ -419,7 +418,7 @@ const deleteRide = async (req, res) => {
}
// Verifica proprietario
if (ride.userId.toString() !== userId) {
if (!ride.userId.equals(userId)) {
return res.status(403).json({
success: false,
message: 'Non sei autorizzato a cancellare questo viaggio'
@@ -463,13 +462,14 @@ const deleteRide = async (req, res) => {
/**
* @desc Ottieni viaggi dell'utente corrente
* @route GET /api/trasporti/rides/my
* @route GET /api/viaggi/rides/my
* @access Private
*/
const getMyRides = async (req, res) => {
try {
const userId = req.user._id;
const { idapp, type, role, status, page = 1, limit = 20 } = req.query;
const idapp = req.query.idapp;
const { type, role, status, page = 1, limit = 20 } = req.query;
let query = { idapp };
@@ -539,12 +539,12 @@ const getMyRides = async (req, res) => {
/**
* @desc Cerca viaggi con match intelligente
* @route GET /api/trasporti/rides/search
* @route GET /api/viaggi/rides/search
* @access Public
*/
const searchRides = async (req, res) => {
try {
const { idapp } = req.query;
const idapp = req.query.idapp;
const {
from,
@@ -659,14 +659,14 @@ const searchRides = async (req, res) => {
/**
* @desc Completa un viaggio
* @route POST /api/trasporti/rides/:id/complete
* @route POST /api/viaggi/rides/:id/complete
* @access Private
*/
const completeRide = async (req, res) => {
try {
const { id } = req.params;
const userId = req.user._id;
const { idapp } = req.body;
const idapp = req.user.idapp;
const ride = await Ride.findOne({ _id: id, idapp });
@@ -677,7 +677,7 @@ const completeRide = async (req, res) => {
});
}
if (ride.userId.toString() !== userId) {
if (!ride.userId.equals(userId)) {
return res.status(403).json({
success: false,
message: 'Solo il conducente può completare il viaggio'
@@ -723,12 +723,12 @@ const completeRide = async (req, res) => {
/**
* @desc Ottieni statistiche viaggi per homepage widget
* @route GET /api/trasporti/rides/stats
* @route GET /api/viaggi/rides/stats
* @access Private
*/
const getRidesStats = async (req, res) => {
try {
const { idapp } = req.query;
const idapp = req.query.idapp;
const userId = req.user._id;
const now = new Date();
@@ -800,7 +800,7 @@ const getRidesStats = async (req, res) => {
/**
* Get aggregated data for dashboard widgets
* GET /api/trasporti/widget/data
* GET /api/viaggi/widget/data
*/
const getWidgetData = async (req, res) => {
try {
@@ -981,7 +981,7 @@ const getWidgetData = async (req, res) => {
/**
* Get comprehensive statistics summary for user
* GET /api/trasporti/stats/summary
* GET /api/viaggi/stats/summary
*/
const getStatsSummary = async (req, res) => {
try {
@@ -1420,7 +1420,7 @@ const getStatsSummary = async (req, res) => {
/**
* Get city suggestions for autocomplete
* GET /api/trasporti/cities/suggestions?q=query
* GET /api/viaggi/cities/suggestions?q=query
*/
const getCitySuggestions = async (req, res) => {
try {
@@ -1679,7 +1679,7 @@ const getCitySuggestions = async (req, res) => {
/**
* Get recent cities from user's trip history
* GET /api/trasporti/cities/recent
* GET /api/viaggi/cities/recent
*/
const getRecentCities = async (req, res) => {
try {

View File

@@ -5,7 +5,7 @@ const Message = require('../models/Message');
/**
* @desc Crea una richiesta di passaggio
* @route POST /api/trasporti/requests
* @route POST /api/viaggi/requests
* @access Private
*/
const createRequest = async (req, res) => {
@@ -154,7 +154,7 @@ const createRequest = async (req, res) => {
/**
* @desc Ottieni le richieste per un viaggio (per il conducente)
* @route GET /api/trasporti/requests/ride/:rideId
* @route GET /api/viaggi/requests/ride/:rideId
* @access Private
*/
const getRequestsForRide = async (req, res) => {
@@ -207,7 +207,7 @@ const getRequestsForRide = async (req, res) => {
/**
* @desc Ottieni le mie richieste (come passeggero)
* @route GET /api/trasporti/requests/my
* @route GET /api/viaggi/requests/my
* @access Private
*/
const getMyRequests = async (req, res) => {
@@ -266,13 +266,13 @@ const getMyRequests = async (req, res) => {
/**
* @desc Ottieni richieste pendenti (per il conducente)
* @route GET /api/trasporti/requests/pending
* @route GET /api/viaggi/requests/pending
* @access Private
*/
const getPendingRequests = async (req, res) => {
try {
const userId = req.user._id;
const { idapp } = req.query;
const idapp = req.user.idapp;
if (!idapp) {
return res.status(400).json({
@@ -307,7 +307,7 @@ const getPendingRequests = async (req, res) => {
/**
* @desc Accetta una richiesta di passaggio
* @route PUT /api/trasporti/requests/:id/accept
* @route PUT /api/viaggi/requests/:id/accept
* @access Private (solo conducente)
*/
const acceptRequest = async (req, res) => {
@@ -403,7 +403,7 @@ const acceptRequest = async (req, res) => {
/**
* @desc Rifiuta una richiesta di passaggio
* @route PUT /api/trasporti/requests/:id/reject
* @route PUT /api/viaggi/requests/:id/reject
* @access Private (solo conducente)
*/
const rejectRequest = async (req, res) => {
@@ -477,7 +477,7 @@ const rejectRequest = async (req, res) => {
/**
* @desc Cancella una richiesta (dal passeggero)
* @route PUT /api/trasporti/requests/:id/cancel
* @route PUT /api/viaggi/requests/:id/cancel
* @access Private
*/
const cancelRequest = async (req, res) => {
@@ -550,7 +550,7 @@ const cancelRequest = async (req, res) => {
/**
* @desc Ottieni una singola richiesta
* @route GET /api/trasporti/requests/:id
* @route GET /api/viaggi/requests/:id
* @access Private
*/
const getRequestById = async (req, res) => {
@@ -604,7 +604,7 @@ const getRequestById = async (req, res) => {
/**
* @desc Ottieni richieste ricevute (io come conducente)
* @route GET /api/trasporti/requests/received
* @route GET /api/viaggi/requests/received
* @access Private
*/
const getReceivedRequests = async (req, res) => {
@@ -684,7 +684,7 @@ const getReceivedRequests = async (req, res) => {
};
/**
* @desc Ottieni richieste inviate (io come passeggero)
* @route GET /api/trasporti/requests/sent
* @route GET /api/viaggi/requests/sent
* @access Private
*/
const getSentRequests = async (req, res) => {

View File

@@ -79,6 +79,16 @@ const ChatSchema = new Schema(
ref: 'User',
},
],
deletedBy: [
{
type: Schema.Types.ObjectId,
ref: 'User',
},
],
clearedBefore: {
type: Map,
of: Date,
},
metadata: {
type: Schema.Types.Mixed,
},
@@ -114,19 +124,17 @@ ChatSchema.methods.getUnreadForUser = function (userId) {
// ✅ FIX: incrementUnread (assicura conversione corretta)
ChatSchema.methods.incrementUnread = function (excludeUserId) {
const excludeIdStr = excludeUserId.toString();
this.participants.forEach((participantId) => {
// Gestisci sia ObjectId che oggetti popolati
const id = participantId._id
? participantId._id.toString()
: participantId.toString();
const id = participantId._id ? participantId._id.toString() : participantId.toString();
if (id !== excludeIdStr) {
const current = this.unreadCount.get(id) || 0;
this.unreadCount.set(id, current + 1);
}
});
return this.save();
};
@@ -151,7 +159,7 @@ ChatSchema.methods.updateLastMessage = function (message) {
// ✅ FIX: Gestisce sia ObjectId che oggetti User popolati
ChatSchema.methods.hasParticipant = function (userId) {
const userIdStr = userId.toString();
return this.participants.some((p) => {
// Se p è un oggetto popolato (ha _id), usa p._id
// Altrimenti p è già un ObjectId
@@ -164,14 +172,13 @@ ChatSchema.methods.hasParticipant = function (userId) {
// ✅ FIX: Metodo isBlockedFor (stesso problema)
ChatSchema.methods.isBlockedFor = function (userId) {
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
ChatSchema.statics.findOrCreateDirect = async function (idapp, userId1, userId2, rideId = null) {
// Cerca chat esistente tra i due utenti

View File

@@ -55,6 +55,10 @@ const LocationSchema = new Schema(
// Schema per i waypoint (tappe intermedie)
const WaypointSchema = new Schema({
/*_id: {
type: String,
required: false
},*/
location: {
type: LocationSchema,
required: true,
@@ -70,7 +74,7 @@ const WaypointSchema = new Schema({
type: Number, // minuti di sosta
default: 0,
},
});
}, { _id: false }); // 👈 AGGIUNGI QUESTO
// Schema per la ricorrenza del viaggio
const RecurrenceSchema = new Schema(

View File

@@ -606,7 +606,6 @@ const UserSchema = new mongoose.Schema(
{
type: {
type: String,
enum: ['auto', 'moto', 'furgone', 'minibus', 'altro'],
default: 'auto',
},
brand: {
@@ -640,7 +639,6 @@ const UserSchema = new mongoose.Schema(
features: [
{
type: String,
enum: ['aria_condizionata', 'wifi', 'presa_usb', 'bluetooth', 'bagagliaio_grande', 'seggiolino_bimbi'],
},
],
photos: [
@@ -704,7 +702,6 @@ const UserSchema = new mongoose.Schema(
},
responseTime: {
type: String,
enum: ['within_hour', 'within_day', 'within_days'],
default: 'within_day',
},
totalKmShared: {
@@ -743,22 +740,18 @@ const UserSchema = new mongoose.Schema(
// Preferenze di viaggio
smoking: {
type: String,
enum: ['yes', 'no', 'outside_only'],
default: 'no',
},
pets: {
type: String,
enum: ['no', 'small', 'medium', 'large', 'all'],
default: 'small',
},
music: {
type: String,
enum: ['no_music', 'quiet', 'moderate', 'loud', 'passenger_choice'],
default: 'moderate',
},
conversation: {
type: String,
enum: ['quiet', 'moderate', 'chatty'],
default: 'moderate',
},
@@ -835,7 +828,6 @@ const UserSchema = new mongoose.Schema(
},
type: {
type: String,
enum: ['home', 'work', 'other'],
default: 'other',
},
},

View File

@@ -13,7 +13,6 @@ const PageView = require('../models/PageView');
const fal = require('@fal-ai/client');
const imageGenerator = require('../services/imageGenerator'); // Assicurati che il percorso sia corretto
const posterEditor = require('../services/PosterEditor'); // <--- Importa la nuova classe
@@ -33,9 +32,8 @@ const { MyElem } = require('../models/myelem');
const axios = require('axios');
const trasportiRoutes = require('../routes/trasportiRoutes');
router.use('/trasporti', trasportiRoutes);
const viaggiRoutes = require('../routes/viaggiRoutes');
router.use('/viaggi', viaggiRoutes);
// Importa le routes video
const videoRoutes = require('../routes/videoRoutes');
@@ -43,7 +41,6 @@ const videoRoutes = require('../routes/videoRoutes');
// Monta le routes video
router.use('/video', videoRoutes);
router.use('/templates', authenticate, templatesRouter);
router.use('/posters', authenticate, postersRouter);
router.use('/assets', authenticate, assetsRouter);
@@ -523,9 +520,16 @@ router.post('/chatbot', authenticate, async (req, res) => {
});
router.post('/generateposter', async (req, res) => {
const {
titolo, data, ora, luogo, descrizione, contatti, fotoDescrizione, stile,
provider = 'hf' // Default a HF (Gratis)
const {
titolo,
data,
ora,
luogo,
descrizione,
contatti,
fotoDescrizione,
stile,
provider = 'hf', // Default a HF (Gratis)
} = req.body;
// 1. Prompt per l'AI: Chiediamo SOLO lo sfondo, VIETIAMO il testo.
@@ -547,21 +551,50 @@ router.post('/generateposter', async (req, res) => {
data,
ora,
luogo,
contatti
contatti,
});
res.json({
success: true,
res.json({
success: true,
imageUrl: finalPosterBase64, // Restituisce l'immagine completa in base64
step: 'AI + Canvas Composition'
step: 'AI + Canvas Composition',
});
} catch (err) {
console.error('Errore:', err.message);
res.status(500).json({ error: err.message });
}
});
router.get('/users/search', authenticate, async (req, res) => {
try {
const { User } = require('../models/user');
const { q, idapp } = req.query;
if (!q || q.length < 2) {
return res.status(400).json({ success: false, message: 'Query too short' });
}
const query = q.trim();
const users = await User.find({
idapp,
$or: [
{ name: { $regex: query, $options: 'i' } },
{ surname: { $regex: query, $options: 'i' } },
{ username: { $regex: query, $options: 'i' } },
],
_id: { $ne: req.user?._id }, // escludi l'utente corrente se autenticato
})
.select('_id name surname username profile') // solo campi necessari
.limit(10); // evita overload
res.json({ success: true, data: users });
} catch (error) {
console.error('User search error:', error);
res.status(500).json({ success: false, message: 'Server error' });
}
});
module.exports = router;

View File

@@ -317,7 +317,11 @@ router.post('/', async (req, res) => {
await telegrambot.askConfirmationUser(myuser.idapp, shared_consts.CallFunz.REGISTRATION, myuser);
const { token, refreshToken, browser_random } = await myuser.generateAuthToken(req, browser_random);
res.header('x-auth', token).header('x-refrtok', refreshToken).header('x-browser-random', browser_random).send(myuser);
res
.header('x-auth', token)
.header('x-refrtok', refreshToken)
.header('x-browser-random', browser_random)
.send(myuser);
return true;
}
}
@@ -368,7 +372,11 @@ router.post('/', async (req, res) => {
// if (!tools.testing()) {
await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg);
// }
res.header('x-auth', ris.token).header('x-refrtok', ris.refreshToken).header('x-browser-random', ris.browser_random).send(user);
res
.header('x-auth', ris.token)
.header('x-refrtok', ris.refreshToken)
.header('x-browser-random', ris.browser_random)
.send(user);
return true;
});
})
@@ -411,7 +419,9 @@ router.patch('/:id', authenticate, (req, res) => {
if (!User.isAdmin(req.user.perm)) {
// If without permissions, exit
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
return res
.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED)
.send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
}
User.findByIdAndUpdate(id, { $set: body })
@@ -512,7 +522,7 @@ router.post('/profile', authenticate, (req, res) => {
try {
// Check if ìs a Notif to read
if (idnotif) {
if (idnotif) {
SendNotif.setNotifAsRead(idapp, usernameOrig, idnotif);
}
@@ -591,9 +601,14 @@ router.post('/panel', authenticate, async (req, res) => {
idapp = req.body.idapp;
locale = req.body.locale;
if (!req.user || !User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm)) {
if (
!req.user ||
(!User.isAdmin(req.user.perm) && !User.isManager(req.user.perm) && !User.isFacilitatore(req.user.perm))
) {
// If without permissions, exit
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
return res
.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED)
.send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
}
try {
@@ -669,7 +684,7 @@ router.post('/newtok', async (req, res) => {
}
const recFound = await User.findByRefreshTokenAnyAccess(refreshToken);
if (!recFound) {
return res.status(403).send({ error: 'Refresh token non valido' });
}
@@ -953,7 +968,9 @@ router.post('/friends/cmd', authenticate, async (req, res) => {
usernameDest !== usernameLogged &&
(cmd === shared_consts.FRIENDSCMD.SETFRIEND || cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE)
) {
return res.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED).send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
return res
.status(server_constants.RIS_CODE_ERR_UNAUTHORIZED)
.send({ code: server_constants.RIS_CODE_ERR_UNAUTHORIZED, msg: '' });
}
}
@@ -1119,7 +1136,10 @@ async function eseguiDbOpUser(idapp, mydata, locale, req, res) {
} else if (mydata.dbop === 'noNameSurname') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noNameSurname': mydata.value } });
} else if (mydata.dbop === 'telegram_verification_skipped') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.telegram_verification_skipped': mydata.value } });
await User.findOneAndUpdate(
{ _id: mydata._id },
{ $set: { 'profile.telegram_verification_skipped': mydata.value } }
);
} else if (mydata.dbop === 'pwdLikeAdmin') {
await User.setPwdComeQuellaDellAdmin(mydata);
} else if (mydata.dbop === 'ripristinaPwdPrec') {
@@ -1129,10 +1149,10 @@ async function eseguiDbOpUser(idapp, mydata, locale, req, res) {
} else if (mydata.dbop === 'noComune') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noComune': mydata.value } });
} else if (mydata.dbop === 'verifiedemail') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'verified_email': mydata.value } });
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { verified_email: mydata.value } });
} else if (mydata.dbop === 'resendVerificationEmail') {
// Invia la email di Verifica email
const ris = await sendemail.sendEmail_ReVerifyingEmail(mydata, idapp);
const ris = await sendemail.sendEmail_ReVerifyingEmail(mydata, idapp);
} else if (mydata.dbop === 'noCircIta') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noCircIta': mydata.value } });
} else if (mydata.dbop === 'insert_circuito_ita') {

37
src/routes/geoRoutes.js Normal file
View File

@@ -0,0 +1,37 @@
const express = require('express');
const router = express.Router();
const {
autocomplete,
geocode,
reverseGeocode,
getRoute,
getMatrix,
suggestWaypoints,
searchItalianCities,
getDistance,
getIsochrone
} = require('../controllers/geocodingController');
// Rate limiting opzionale
const rateLimit = require('express-rate-limit');
const geoLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minuto
max: 60, // 60 richieste per minuto
message: { success: false, message: 'Troppe richieste, riprova tra poco' }
});
router.use(geoLimiter);
// Routes
router.get('/autocomplete', autocomplete);
router.get('/geocode', geocode);
router.get('/reverse', reverseGeocode);
router.get('/route', getRoute);
router.post('/matrix', getMatrix);
router.get('/suggest-waypoints', suggestWaypoints);
router.get('/cities/it', searchItalianCities);
router.get('/distance', getDistance);
router.get('/isochrone', getIsochrone);
module.exports = router;

View File

@@ -14,7 +14,12 @@ const rideController = require('../controllers/rideController');
const rideRequestController = require('../controllers/rideRequestController');
const chatController = require('../controllers/chatController');
const feedbackController = require('../controllers/feedbackController');
const geocodingController = require('../controllers/geocodingController');
// const geocodingController = require('../controllers/geocodingController');
const geoRoutes = require('./geoRoutes'); // 👈 Importa geoRoutes
router.use('/geo', geoRoutes); // 👈 Monta come sub-router
// Middleware di autenticazione (usa il tuo esistente)
const { authenticate } = require('../middleware/authenticate');
@@ -24,42 +29,42 @@ const { authenticate } = require('../middleware/authenticate');
// ============================================================
/**
* @route POST /api/trasporti/rides
* @route POST /api/viaggi/rides
* @desc Crea nuovo viaggio (offerta o richiesta)
* @access Private
*/
router.post('/rides', authenticate, rideController.createRide);
/**
* @route GET /api/trasporti/rides
* @route GET /api/viaggi/rides
* @desc Ottieni lista viaggi con filtri
* @access Public
*/
router.get('/rides', rideController.getRides);
/**
* @route GET /api/trasporti/rides/search
* @route GET /api/viaggi/rides/search
* @desc Ricerca viaggi avanzata
* @access Public
*/
router.get('/rides/search', rideController.searchRides);
/**
* @route GET /api/trasporti/rides/stats
* @route GET /api/viaggi/rides/stats
* @desc Statistiche per widget homepage
* @access Private
*/
router.get('/rides/stats', authenticate, rideController.getRidesStats);
/**
* @route GET /api/trasporti/rides/my
* @route GET /api/viaggi/rides/my
* @desc I miei viaggi (come driver e passenger)
* @access Private
*/
router.get('/rides/my', authenticate, rideController.getMyRides);
/**
* @route GET /api/trasporti/rides/match
* @route GET /api/viaggi/rides/match
* @desc Match automatico offerta/richiesta
* @access Private
* @note IMPORTANTE: Questa route DEVE stare PRIMA di /rides/:id
@@ -67,28 +72,28 @@ router.get('/rides/my', authenticate, rideController.getMyRides);
//router.get('/rides/match', authenticate, rideController.findMatches);
/**
* @route GET /api/trasporti/rides/:id
* @route GET /api/viaggi/rides/:id
* @desc Dettaglio singolo viaggio
* @access Public
*/
router.get('/rides/:id', rideController.getRideById);
/**
* @route PUT /api/trasporti/rides/:id
* @route PUT /api/viaggi/rides/:id
* @desc Aggiorna viaggio
* @access Private (solo owner)
*/
router.put('/rides/:id', authenticate, rideController.updateRide);
/**
* @route DELETE /api/trasporti/rides/:id
* @route DELETE /api/viaggi/rides/:id
* @desc Cancella viaggio
* @access Private (solo owner)
*/
router.delete('/rides/:id', authenticate, rideController.deleteRide);
/**
* @route POST /api/trasporti/rides/:id/complete
* @route POST /api/viaggi/rides/:id/complete
* @desc Completa un viaggio
* @access Private (solo driver)
*/
@@ -99,28 +104,28 @@ router.post('/rides/:id/complete', authenticate, rideController.completeRide);
// ============================================================
/**
* @route GET /api/trasporti/widget/data
* @route GET /api/viaggi/widget/data
* @desc Dati completi per widget homepage
* @access Private
*/
router.get('/widget/data', authenticate, rideController.getWidgetData);
/**
* @route GET /api/trasporti/stats/summary
* @route GET /api/viaggi/stats/summary
* @desc Stats rapide per header widget (offerte, richieste, match)
* @access Public
*/
router.get('/stats/summary', authenticate, rideController.getStatsSummary);
/**
* @route GET /api/trasporti/cities/suggestions
* @route GET /api/viaggi/cities/suggestions
* @desc Suggerimenti città per autocomplete (basato su viaggi esistenti)
* @access Public
*/
router.get('/cities/suggestions', rideController.getCitySuggestions);
/**
* @route GET /api/trasporti/cities/recents
* @route GET /api/viaggi/cities/recents
* @desc città recenti per autocomplete
* @access Public
*/
@@ -131,56 +136,56 @@ router.get('/cities/recent', authenticate, rideController.getRecentCities);
// ============================================================
/**
* @route POST /api/trasporti/requests
* @route POST /api/viaggi/requests
* @desc Crea richiesta passaggio per un viaggio
* @access Private
*/
router.post('/requests', authenticate, rideRequestController.createRequest);
/**
* @route GET /api/trasporti/requests/received
* @route GET /api/viaggi/requests/received
* @desc Richieste ricevute (sono conducente)
* @access Private
*/
router.get('/requests/received', authenticate, rideRequestController.getReceivedRequests);
/**
* @route GET /api/trasporti/requests/sent
* @route GET /api/viaggi/requests/sent
* @desc Richieste inviate (sono passeggero)
* @access Private
*/
router.get('/requests/sent', authenticate, rideRequestController.getSentRequests);
/**
* @route GET /api/trasporti/requests/ride/:rideId
* @route GET /api/viaggi/requests/ride/:rideId
* @desc Richieste per un viaggio specifico
* @access Private (solo owner del viaggio)
*/
router.get('/requests/ride/:rideId', authenticate, rideRequestController.getRequestsForRide);
/**
* @route GET /api/trasporti/requests/:id
* @route GET /api/viaggi/requests/:id
* @desc Dettaglio singola richiesta
* @access Private (driver o passenger)
*/
router.get('/requests/:id', authenticate, rideRequestController.getRequestById);
/**
* @route POST /api/trasporti/requests/:id/accept
* @route POST /api/viaggi/requests/:id/accept
* @desc Accetta richiesta passaggio
* @access Private (solo driver)
*/
router.post('/requests/:id/accept', authenticate, rideRequestController.acceptRequest);
/**
* @route POST /api/trasporti/requests/:id/reject
* @route POST /api/viaggi/requests/:id/reject
* @desc Rifiuta richiesta passaggio
* @access Private (solo driver)
*/
router.post('/requests/:id/reject', authenticate, rideRequestController.rejectRequest);
/**
* @route POST /api/trasporti/requests/:id/cancel
* @route POST /api/viaggi/requests/:id/cancel
* @desc Cancella richiesta/prenotazione
* @access Private (driver o passenger)
*/
@@ -191,72 +196,72 @@ router.post('/requests/:id/cancel', authenticate, rideRequestController.cancelRe
// ============================================================
/**
* @route GET /api/trasporti/chats
* @route GET /api/viaggi/chats
* @desc Lista tutte le mie chat
* @access Private
*/
router.get('/chats', authenticate, chatController.getMyChats);
router.get('/chats', authenticate, chatController.getUserChats);
/**
* @route GET /api/trasporti/chats/unread/count
* @route GET /api/viaggi/chats/unread/count
* @desc Conta messaggi non letti totali
* @access Private
*/
router.get('/chats/unread/count', authenticate, chatController.getUnreadCount);
/**
* @route POST /api/trasporti/chats/direct
* @route POST /api/viaggi/chats/direct
* @desc Ottieni o crea chat diretta con utente
* @access Private
*/
router.post('/chats/direct', authenticate, chatController.getOrCreateDirectChat);
/**
* @route GET /api/trasporti/chats/:id
* @route GET /api/viaggi/chats/:id
* @desc Dettaglio chat
* @access Private (solo partecipanti)
*/
router.get('/chats/:id', authenticate, chatController.getChatById);
router.get('/chats/:chatId', authenticate, chatController.getChatById);
/**
* @route GET /api/trasporti/chats/:id/messages
* @route GET /api/viaggi/chats/:id/messages
* @desc Messaggi di una chat
* @access Private (solo partecipanti)
*/
router.get('/chats/:id/messages', authenticate, chatController.getChatMessages);
router.get('/chats/:chatId/messages', authenticate, chatController.getChatMessages);
/**
* @route POST /api/trasporti/chats/:id/messages
* @route POST /api/viaggi/chats/:id/messages
* @desc Invia messaggio
* @access Private (solo partecipanti)
*/
router.post('/chats/:id/messages', authenticate, chatController.sendMessage);
router.post('/chats/:chatId/messages', authenticate, chatController.sendMessage);
/**
* @route PUT /api/trasporti/chats/:id/read
* @route PUT /api/viaggi/chats/:id/read
* @desc Segna chat come letta
* @access Private (solo partecipanti)
* @fix Corretto: markAsRead markChatAsRead
*/
router.put('/chats/:id/read', authenticate, chatController.markChatAsRead);
router.put('/chats/:chatId/read', authenticate, chatController.markChatAsRead);
/**
* @route PUT /api/trasporti/chats/:id/block
* @route PUT /api/viaggi/chats/:id/block
* @desc Blocca/sblocca chat
* @access Private (solo partecipanti)
*/
router.put('/chats/:id/block', authenticate, chatController.toggleBlockChat);
router.put('/chats/:chatId/block', authenticate, chatController.toggleBlockChat);
/**
* @route PUT /api/trasporti/chats/:id/mute
* @route PUT /api/viaggi/chats/:id/mute
* @desc Muta/smuta notifiche di una chat
* @access Private (solo partecipanti)
* @fix Aggiunta route mancante
*/
router.put('/chats/:id/mute', authenticate, chatController.toggleMuteChat);
router.put('/chats/:chatId/mute', authenticate, chatController.toggleMuteChat);
/**
* @route DELETE /api/trasporti/chats/:chatId/messages/:messageId
* @route DELETE /api/viaggi/chats/:chatId/messages/:messageId
* @desc Elimina messaggio
* @access Private (solo mittente)
* @fix Corretto: /messages/:id /chats/:chatId/messages/:messageId
@@ -264,81 +269,81 @@ router.put('/chats/:id/mute', authenticate, chatController.toggleMuteChat);
router.delete('/chats/:chatId/messages/:messageId', authenticate, chatController.deleteMessage);
/**
* @route DELETE /api/trasporti/chats/:id
* @route DELETE /api/viaggi/chats/:id
* @desc Elimina chat (soft delete)
* @access Private (solo partecipanti)
*/
router.delete('/chats/:id', authenticate, chatController.deleteChat);
router.delete('/chats/:chatId', authenticate, chatController.deleteChat);
// ============================================================
// ⭐ FEEDBACK - Recensioni
// ============================================================
/**
* @route POST /api/trasporti/feedback
* @route POST /api/viaggi/feedback
* @desc Crea feedback per un viaggio
* @access Private
*/
router.post('/feedback', authenticate, feedbackController.createFeedback);
/**
* @route GET /api/trasporti/feedback/my/received
* @route GET /api/viaggi/feedback/my/received
* @desc I miei feedback ricevuti
* @access Private
*/
router.get('/feedback/my/received', authenticate, feedbackController.getMyReceivedFeedbacks);
/**
* @route GET /api/trasporti/feedback/my/given
* @route GET /api/viaggi/feedback/my/given
* @desc I miei feedback lasciati
* @access Private
*/
router.get('/feedback/my/given', authenticate, feedbackController.getMyGivenFeedbacks);
/**
* @route GET /api/trasporti/feedback/user/:userId
* @route GET /api/viaggi/feedback/user/:userId
* @desc Feedback di un utente
* @access Public
*/
router.get('/feedback/user/:userId', feedbackController.getFeedbacksForUser);
/**
* @route GET /api/trasporti/feedback/user/:userId/stats
* @route GET /api/viaggi/feedback/user/:userId/stats
* @desc Statistiche feedback utente
* @access Public
*/
router.get('/feedback/user/:userId/stats', feedbackController.getUserFeedbackStats);
/**
* @route GET /api/trasporti/feedback/ride/:rideId
* @route GET /api/viaggi/feedback/ride/:rideId
* @desc Feedback per un viaggio
* @access Public
*/
router.get('/feedback/ride/:rideId', feedbackController.getRideFeedback);
/**
* @route GET /api/trasporti/feedback/can-leave/:rideId/:toUserId
* @route GET /api/viaggi/feedback/can-leave/:rideId/:toUserId
* @desc Verifica se posso lasciare feedback
* @access Private
*/
router.get('/feedback/can-leave/:rideId/:toUserId', authenticate, feedbackController.canLeaveFeedback);
/**
* @route POST /api/trasporti/feedback/:id/response
* @route POST /api/viaggi/feedback/:id/response
* @desc Rispondi a un feedback
* @access Private (solo destinatario)
*/
router.post('/feedback/:id/response', authenticate, feedbackController.respondToFeedback);
/**
* @route POST /api/trasporti/feedback/:id/report
* @route POST /api/viaggi/feedback/:id/report
* @desc Segnala feedback
* @access Private
*/
router.post('/feedback/:id/report', authenticate, feedbackController.reportFeedback);
/**
* @route POST /api/trasporti/feedback/:id/helpful
* @route POST /api/viaggi/feedback/:id/helpful
* @desc Segna feedback come utile
* @access Private
*/
@@ -348,68 +353,68 @@ router.post('/feedback/:id/helpful', authenticate, feedbackController.markAsHelp
// 🗺️ GEO - Geocoding & Mappe (Open Source)
// ============================================================
/**
* @route GET /api/trasporti/geo/autocomplete
* @desc Autocomplete città (Photon)
* @access Public
*/
router.get('/geo/autocomplete', geocodingController.autocomplete);
// /* /**
// * @route GET /api/viaggi/geo/autocomplete
// * @desc Autocomplete città (Photon)
// * @access Public
// */
// router.get('/geo/autocomplete', geocodingController.autocomplete);
/**
* @route GET /api/trasporti/geo/cities/it
* @desc Cerca città italiane
* @access Public
*/
router.get('/geo/cities/it', geocodingController.searchItalianCities);
// /**
// * @route GET /api/viaggi/geo/cities/it
// * @desc Cerca città italiane
// * @access Public
// */
// router.get('/geo/cities/it', geocodingController.searchItalianCities);
/**
* @route GET /api/trasporti/geo/geocode
* @desc Indirizzo Coordinate
* @access Public
*/
router.get('/geo/geocode', geocodingController.geocode);
// /**
// * @route GET /api/viaggi/geo/geocode
// * @desc IndirizzoCoordinate
// * @access Public
// */
// router.get('/geo/geocode', geocodingController.geocode);
/**
* @route GET /api/trasporti/geo/reverse
* @desc Coordinate Indirizzo
* @access Public
*/
router.get('/geo/reverse', geocodingController.reverseGeocode);
// /**
// * @route GET /api/viaggi/geo/reverse
// * @desc CoordinateIndirizzo
// * @access Public
// */
// router.get('/geo/reverse', geocodingController.reverseGeocode);
/**
* @route GET /api/trasporti/geo/route
* @desc Calcola percorso tra punti
* @access Public
*/
router.get('/geo/route', geocodingController.getRoute);
// /**
// * @route GET /api/viaggi/geo/route
// * @desc Calcola percorso tra punti
// * @access Public
// */
// router.get('/geo/route', geocodingController.getRoute);
/**
* @route GET /api/trasporti/geo/distance
* @desc Calcola distanza e durata
* @access Public
*/
router.get('/geo/distance', geocodingController.getDistance);
// /**
// * @route GET /api/viaggi/geo/distance
// * @desc Calcola distanza e durata
// * @access Public
// */
// router.get('/geo/distance', geocodingController.getDistance);
/**
* @route GET /api/trasporti/geo/suggest-waypoints
* @desc Suggerisci città intermedie sul percorso
* @access Public
*/
router.get('/geo/suggest-waypoints', geocodingController.suggestWaypoints);
// /**
// * @route GET /api/viaggi/geo/suggest-waypoints
// * @desc Suggerisci città intermedie sul percorso
// * @access Public
// */
// router.get('/geo/suggest-waypoints', geocodingController.suggestWaypoints);
// ============================================================
/// ============================================================
// 🔧 UTILITY & DRIVER PROFILE
// ============================================================
/**
* @route GET /api/trasporti/driver/user/:userId
* @route GET /api/viaggi/driver/user/:userId
* @desc Profilo pubblico del conducente
* @access Public
*/
router.get('/driver/user/:userId', async (req, res) => {
try {
const { userId } = req.params;
const { idapp } = req.query;
const idapp = req.user.idapp;
const { User } = require('../models/user');
const Ride = require('../models/Ride');
@@ -502,13 +507,13 @@ router.get('/driver/user/:userId', async (req, res) => {
});
/**
* @route GET /api/trasporti/driver/vehicles
* @route GET /api/viaggi/driver/vehicles
* @desc Ottieni veicoli dell'utente corrente
* @access Private
*/
router.get('/driver/vehicles', authenticate, async (req, res) => {
try {
const { idapp } = req.query;
const idapp = req.user.idapp;
const userId = req.user._id; // Assumo che ci sia un middleware di autenticazione
if (!userId) {
@@ -539,7 +544,7 @@ router.get('/driver/vehicles', authenticate, async (req, res) => {
});
/**
* @route PUT /api/trasporti/driver/profile
* @route PUT /api/viaggi/driver/profile
* @desc Aggiorna profilo conducente
* @access Private
*/
@@ -585,14 +590,14 @@ router.put('/driver/profile', authenticate, async (req, res) => {
});
/**
* @route POST /api/trasporti/driver/vehicles
* @route POST /api/viaggi/driver/vehicles
* @desc Aggiungi veicolo
* @access Private
*/
router.post('/driver/vehicles', authenticate, async (req, res) => {
try {
const userId = req.user._id;
const vehicle = req.body;
const vehicle = req.body.vehicle ? req.body.vehicle : req.body;
const { User } = require('../models/user');
@@ -621,7 +626,7 @@ router.post('/driver/vehicles', authenticate, async (req, res) => {
});
/**
* @route PUT /api/trasporti/driver/vehicles/:vehicleId
* @route PUT /api/viaggi/driver/vehicles/:vehicleId
* @desc Aggiorna veicolo
* @access Private
*/
@@ -667,7 +672,7 @@ router.put('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
});
/**
* @route GET /api/trasporti/driver/vehicles/:vehicleId
* @route GET /api/viaggi/driver/vehicles/:vehicleId
* @desc Ottieni dettagli di un veicolo specifico
* @access Private
*/
@@ -715,7 +720,7 @@ router.get('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
});
/**
* @route DELETE /api/trasporti/driver/vehicles/:vehicleId
* @route DELETE /api/viaggi/driver/vehicles/:vehicleId
* @desc Rimuovi veicolo
* @access Private
*/
@@ -745,7 +750,7 @@ router.delete('/driver/vehicles/:vehicleId', authenticate, async (req, res) => {
});
/**
* @route POST /api/trasporti/driver/vehicles/:vehicleId/default
* @route POST /api/viaggi/driver/vehicles/:vehicleId/default
* @desc Imposta veicolo come predefinito
* @access Private
*/
@@ -784,13 +789,13 @@ router.post('/driver/vehicles/:vehicleId/default', authenticate, async (req, res
// ============================================================
/**
* @route GET /api/trasporti/contrib-types
* @route GET /api/viaggi/contrib-types
* @desc Lista tipi di contributo disponibili
* @access Public
*/
router.get('/contrib-types', async (req, res) => {
try {
const { idapp } = req.query;
const idapp = req.query.idapp;
const contribTypes = await Contribtype.find({ idapp });
@@ -848,7 +853,7 @@ const uploadVehiclePhoto = multer({
});
/**
* @route POST /api/trasporti/upload/vehicle-photos
* @route POST /api/viaggi/upload/vehicle-photos
* @desc Upload multiple foto veicolo (max 5)
* @access Private
*/
@@ -913,7 +918,7 @@ router.post(
);
/**
* @route POST /api/trasporti/upload/vehicle-photo
* @route POST /api/viaggi/upload/vehicle-photo
* @desc Upload foto veicolo
* @access Private
*/
@@ -977,7 +982,7 @@ router.post(
);
/**
* @route DELETE /api/trasporti/upload/vehicle-photo
* @route DELETE /api/viaggi/upload/vehicle-photo
* @desc Elimina foto veicolo
* @access Private
*/

View File

@@ -13,7 +13,7 @@ function setupExpress(app, corsOptions) {
app.use(helmet());
app.use(morgan('dev'));
app.use(cors(corsOptions));
app.set('trust proxy', true);
app.set('trust proxy', (process.env.NODE_ENV === 'development') ? false : true);
// parser
app.use(express.json({ limit: '100mb' }));