- 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

@@ -44,3 +44,4 @@ MIAB_ADMIN_PASSWORD=passpao1pabox@1A
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7" DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
SERVER_A_URL="http://51.77.156.69:3000" 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 SCRIPTS_DIR=admin_scripts
CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}] CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpYgZCw-TViM2wX6vB3wlK6GD0"},{"label":"gruppomacro.com","value":"bqmzGShoX7WqOBzkXocoECyBkPq3GfqcM5t6VFd8"}]
DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7" DS_API_KEY="sk-222e3addb3d8455d8b0516d93906eec7"
ORS_API_KEY="eyJvcmciOiI1YjNjZTM1OTc4NTExMTAwMDFjZjYyNDgiLCJpZCI6IjNjNTllZmY1ZTM1ZDQ5ODI5NThhOTIzYTQ5MDkxOWIwIiwiaCI6Im11cm11cjY0In0="

View File

@@ -45,3 +45,4 @@ GROK_API="xai-PcNM5obgPaETtmnfDWPZk235D75ZgxENU2QmeqPfMQCHh9dwCDVeRRe0oVVA2YOpiU
REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u" REPLICATE_API_TOKEN="r8_AVhM6igwvoOnUA65cHVZdhEDfTqBVk94WTB0u"
FAL_KEY="7d251c88-21b5-4b55-8b3e-4bafd910f99f:b81c0a36a25b052f26eb8ac226c7efff" 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

@@ -42,3 +42,4 @@ MIAB_ADMIN_EMAIL=admin@lamiaposta.org
MIAB_ADMIN_PASSWORD=passpao1pabox@1A MIAB_ADMIN_PASSWORD=passpao1pabox@1A
SERVER_A_URL="http://51.77.156.69:3000" 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 @@ CLOUDFLARE_TOKENS=[{"label":"Paolo.arena77@gmail.com","value":"M9EM309v8WFquJKpY
MIAB_HOST=box.lamiaposta.org MIAB_HOST=box.lamiaposta.org
MIAB_ADMIN_EMAIL=admin@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 // 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) { } catch (error) {
console.error('Error in registration:', error.message); console.error('Error in registration:', error.message);
res.status(400).send({ res.status(400).send({
@@ -103,11 +107,15 @@ class UserController {
} }
// Send response with tokens // Send response with tokens
res.header('x-auth', result.token).header('x-refrtok', result.refreshToken).header('x-browser-random', result.browser_random).send({ res
usertosend: result.user, .header('x-auth', result.token)
code: server_constants.RIS_CODE_OK, .header('x-refrtok', result.refreshToken)
subsExistonDb: result.subsExistonDb, .header('x-browser-random', result.browser_random)
}); .send({
usertosend: result.user,
code: server_constants.RIS_CODE_OK,
subsExistonDb: result.subsExistonDb,
});
} catch (error) { } catch (error) {
console.error('Error in login:', error.message); console.error('Error in login:', error.message);
res.status(400).send({ res.status(400).send({
@@ -487,6 +495,7 @@ class UserController {
const { User } = require('../models/user'); const { User } = require('../models/user');
return User.isCollaboratore(user.perm); return User.isCollaboratore(user.perm);
} }
} }
module.exports = UserController; 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 * @desc Crea un feedback per un viaggio
* @route POST /api/trasporti/feedback * @route POST /api/viaggi/feedback
* @access Private * @access Private
*/ */
const createFeedback = async (req, res) => { const createFeedback = async (req, res) => {
@@ -144,7 +144,7 @@ const createFeedback = async (req, res) => {
/** /**
* @desc Ottieni i feedback ricevuti da un utente * @desc Ottieni i feedback ricevuti da un utente
* @route GET /api/trasporti/feedback/user/:userId * @route GET /api/viaggi/feedback/user/:userId
* @access Public * @access Public
*/ */
const getUserFeedback = async (req, res) => { const getUserFeedback = async (req, res) => {
@@ -206,13 +206,13 @@ const getUserFeedback = async (req, res) => {
/** /**
* @desc Ottieni statistiche feedback per un utente * @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 * @access Public
*/ */
const getUserFeedbackStats = async (req, res) => { const getUserFeedbackStats = async (req, res) => {
try { try {
const { userId } = req.params; const { userId } = req.params;
const { idapp } = req.query; const idapp = req.user.idapp;
if (!idapp) { if (!idapp) {
return res.status(400).json({ return res.status(400).json({
@@ -246,13 +246,13 @@ const getUserFeedbackStats = async (req, res) => {
/** /**
* @desc Ottieni i feedback per un viaggio * @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) * @access Public (con info limitate) / Private (info complete)
*/ */
const getRideFeedback = async (req, res) => { const getRideFeedback = async (req, res) => {
try { try {
const { rideId } = req.params; const { rideId } = req.params;
const { idapp } = req.query; const idapp = req.user.idapp;
const userId = req.user?._id; const userId = req.user?._id;
if (!idapp) { if (!idapp) {
@@ -337,14 +337,14 @@ const getRideFeedback = async (req, res) => {
/** /**
* @desc Verifica se l'utente può lasciare un feedback * @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 * @access Private
* @note NUOVA FUNZIONE - Era mancante! * @note NUOVA FUNZIONE - Era mancante!
*/ */
const canLeaveFeedback = async (req, res) => { const canLeaveFeedback = async (req, res) => {
try { try {
const { rideId, toUserId } = req.params; const { rideId, toUserId } = req.params;
const { idapp } = req.query; const idapp = req.user.idapp;
const fromUserId = req.user._id; const fromUserId = req.user._id;
if (!idapp) { if (!idapp) {
@@ -476,7 +476,7 @@ const canLeaveFeedback = async (req, res) => {
/** /**
* @desc Rispondi a un feedback ricevuto * @desc Rispondi a un feedback ricevuto
* @route POST /api/trasporti/feedback/:id/response * @route POST /api/viaggi/feedback/:id/response
* @access Private * @access Private
*/ */
const respondToFeedback = async (req, res) => { const respondToFeedback = async (req, res) => {
@@ -542,7 +542,7 @@ const respondToFeedback = async (req, res) => {
/** /**
* @desc Segna un feedback come utile * @desc Segna un feedback come utile
* @route POST /api/trasporti/feedback/:id/helpful * @route POST /api/viaggi/feedback/:id/helpful
* @access Private * @access Private
*/ */
const markAsHelpful = async (req, res) => { const markAsHelpful = async (req, res) => {
@@ -605,7 +605,7 @@ const markAsHelpful = async (req, res) => {
/** /**
* @desc Segnala un feedback inappropriato * @desc Segnala un feedback inappropriato
* @route POST /api/trasporti/feedback/:id/report * @route POST /api/viaggi/feedback/:id/report
* @access Private * @access Private
*/ */
const reportFeedback = async (req, res) => { const reportFeedback = async (req, res) => {
@@ -679,7 +679,7 @@ const reportFeedback = async (req, res) => {
/** /**
* @desc Ottieni i miei feedback dati * @desc Ottieni i miei feedback dati
* @route GET /api/trasporti/feedback/my/given * @route GET /api/viaggi/feedback/my/given
* @access Private * @access Private
*/ */
const getMyGivenFeedback = async (req, res) => { const getMyGivenFeedback = async (req, res) => {
@@ -729,7 +729,7 @@ const getMyGivenFeedback = async (req, res) => {
/** /**
* @desc Ottieni i miei feedback ricevuti * @desc Ottieni i miei feedback ricevuti
* @route GET /api/trasporti/feedback/my/received * @route GET /api/viaggi/feedback/my/received
* @access Private * @access Private
*/ */
const getMyReceivedFeedback = async (req, res) => { const getMyReceivedFeedback = async (req, res) => {

View File

@@ -1,39 +1,43 @@
/** /**
* Controller per Geocoding usando servizi Open Source * Controller per Geocoding usando OpenRouteService
* - Nominatim (OpenStreetMap) per geocoding/reverse * Documentazione: https://openrouteservice.org/dev/#/api-docs
* - OSRM per routing
* - Photon per autocomplete
*/ */
const https = require('https'); const https = require('https');
const http = require('http');
// Configurazione servizi // Configurazione OpenRouteService
const NOMINATIM_BASE = 'https://nominatim.openstreetmap.org'; const ORS_BASE = 'https://api.openrouteservice.org';
const PHOTON_BASE = 'https://photon.komoot.io'; const ORS_API_KEY = process.env.ORS_API_KEY || 'YOUR_API_KEY_HERE';
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 * Helper per fare richieste HTTPS a OpenRouteService
*/ */
const makeRequest = (url) => { const makeRequest = (url, method = 'GET', body = null) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http; const urlObj = new URL(url);
const req = client.get(url, { const options = {
hostname: urlObj.hostname,
path: urlObj.pathname + urlObj.search,
method,
headers: { headers: {
'User-Agent': USER_AGENT, Authorization: ORS_API_KEY,
'Accept': 'application/json' Accept: 'application/json',
} 'Content-Type': 'application/json',
}, (res) => { },
};
const req = https.request(options, (res) => {
let data = ''; let data = '';
res.on('data', chunk => data += chunk); res.on('data', (chunk) => (data += chunk));
res.on('end', () => { res.on('end', () => {
try { 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) { } catch (e) {
reject(new Error('Errore parsing risposta')); reject(new Error('Errore parsing risposta'));
} }
@@ -41,338 +45,436 @@ const makeRequest = (url) => {
}); });
req.on('error', reject); req.on('error', reject);
req.setTimeout(10000, () => { req.setTimeout(15000, () => {
req.destroy(); req.destroy();
reject(new Error('Timeout richiesta')); reject(new Error('Timeout richiesta'));
}); });
if (body) {
req.write(JSON.stringify(body));
}
req.end();
}); });
}; };
/** /**
* @desc Autocomplete città (Photon API) * @desc Autocomplete città (ORS Geocode Autocomplete)
* @route GET /api/trasporti/geo/autocomplete * @route GET /api/geo/autocomplete
*/ */
const autocomplete = async (req, res) => { const autocomplete = async (req, res) => {
try { try {
const { q, limit = 5, lang = 'it' } = req.query; const { q, limit = 5, lang = 'it', country = 'IT' } = req.query;
if (!q || q.length < 2) { if (!q || q.length < 2) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Query deve essere almeno 2 caratteri' message: 'Query deve essere almeno 2 caratteri',
}); });
} }
// Photon API - gratuito e veloce const params = new URLSearchParams({
const url = `${PHOTON_BASE}/api/?q=${encodeURIComponent(q)}&limit=${limit}&lang=${lang}&osm_tag=place:city&osm_tag=place:town&osm_tag=place:village`; 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); 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, city: feature.properties.name,
province: feature.properties.county || feature.properties.state, locality: feature.properties.locality,
region: feature.properties.state, county: feature.properties.county,
region: feature.properties.region,
country: feature.properties.country, country: feature.properties.country,
postalCode: feature.properties.postcode, postalCode: feature.properties.postalcode,
coordinates: { coordinates: {
lat: feature.geometry.coordinates[1], lat: feature.geometry.coordinates[1],
lng: feature.geometry.coordinates[0] lng: feature.geometry.coordinates[0],
}, },
displayName: [ displayName: feature.properties.label,
feature.properties.name, type: feature.properties.layer,
feature.properties.county, confidence: feature.properties.confidence,
feature.properties.state,
feature.properties.country
].filter(Boolean).join(', '),
type: feature.properties.osm_value || 'place'
})); }));
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: results count: results.length,
data: results,
}); });
} catch (error) { } catch (error) {
console.error('Errore autocomplete:', error); console.error('Errore autocomplete:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante la ricerca', message: 'Errore durante la ricerca',
error: error.message error: error.message,
}); });
} }
}; };
/** /**
* @desc Geocoding - indirizzo a coordinate (Nominatim) * @desc Geocoding - indirizzo a coordinate (ORS Geocode Search)
* @route GET /api/trasporti/geo/geocode * @route GET /api/geo/geocode
*/ */
const geocode = async (req, res) => { const geocode = async (req, res) => {
try { 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) { if (!searchQuery) {
return res.status(400).json({ return res.status(400).json({
success: false, 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); const data = await makeRequest(url);
if (!data || data.length === 0) { if (!data.features || data.features.length === 0) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: 'Nessun risultato trovato' message: 'Nessun risultato trovato',
}); });
} }
const results = data.map(item => ({ const results = data.features.map((feature) => ({
displayName: item.display_name, id: feature.properties.id,
city: item.address.city || item.address.town || item.address.village || item.address.municipality, displayName: feature.properties.label,
address: item.address.road ? `${item.address.road}${item.address.house_number ? ' ' + item.address.house_number : ''}` : null, name: feature.properties.name,
province: item.address.county || item.address.province, street: feature.properties.street,
region: item.address.state, houseNumber: feature.properties.housenumber,
country: item.address.country, city: feature.properties.locality || feature.properties.county,
postalCode: item.address.postcode, county: feature.properties.county,
region: feature.properties.region,
country: feature.properties.country,
postalCode: feature.properties.postalcode,
coordinates: { coordinates: {
lat: parseFloat(item.lat), lat: feature.geometry.coordinates[1],
lng: parseFloat(item.lon) lng: feature.geometry.coordinates[0],
}, },
type: item.type, type: feature.properties.layer,
importance: item.importance confidence: feature.properties.confidence,
})); }));
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: results count: results.length,
data: results,
}); });
} catch (error) { } catch (error) {
console.error('Errore geocoding:', error); console.error('Errore geocoding:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante il geocoding', message: 'Errore durante il geocoding',
error: error.message error: error.message,
}); });
} }
}; };
/** /**
* @desc Reverse geocoding - coordinate a indirizzo (Nominatim) * @desc Reverse geocoding - coordinate a indirizzo (ORS Reverse)
* @route GET /api/trasporti/geo/reverse * @route GET /api/geo/reverse
*/ */
const reverseGeocode = async (req, res) => { const reverseGeocode = async (req, res) => {
try { try {
const { lat, lng } = req.query; const { lat, lng, lang = 'it' } = req.query;
if (!lat || !lng) { if (!lat || !lng) {
return res.status(400).json({ return res.status(400).json({
success: false, 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); const data = await makeRequest(url);
if (!data || data.error) { if (!data.features || data.features.length === 0) {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: 'Nessun risultato trovato' message: 'Nessun risultato trovato',
}); });
} }
const feature = data.features[0];
const result = { const result = {
displayName: data.display_name, displayName: feature.properties.label,
city: data.address.city || data.address.town || data.address.village || data.address.municipality, name: feature.properties.name,
address: data.address.road ? `${data.address.road}${data.address.house_number ? ' ' + data.address.house_number : ''}` : null, street: feature.properties.street,
province: data.address.county || data.address.province, houseNumber: feature.properties.housenumber,
region: data.address.state, city: feature.properties.locality || feature.properties.county,
country: data.address.country, county: feature.properties.county,
postalCode: data.address.postcode, region: feature.properties.region,
country: feature.properties.country,
postalCode: feature.properties.postalcode,
coordinates: { coordinates: {
lat: parseFloat(lat), lat: parseFloat(lat),
lng: parseFloat(lng) lng: parseFloat(lng),
} },
distance: feature.properties.distance, // distanza dal punto esatto
}; };
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: result data: result,
}); });
} catch (error) { } catch (error) {
console.error('Errore reverse geocoding:', error); console.error('Errore reverse geocoding:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante il reverse geocoding', message: 'Errore durante il reverse geocoding',
error: error.message error: error.message,
}); });
} }
}; };
/** /**
* @desc Calcola percorso tra due punti (OSRM) * @desc Calcola percorso tra due o più punti (ORS Directions)
* @route GET /api/trasporti/geo/route * @route POST /api/geo/route
* @body { coordinates: [[lng,lat], [lng,lat], ...], profile: 'driving-car' }
*/ */
const getRoute = async (req, res) => { const getRoute = async (req, res) => {
try { try {
const { const {
startLat, startLng, startLat,
endLat, endLng, startLng,
waypoints // formato: "lat1,lng1;lat2,lng2;..." 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; } = req.query;
if (!startLat || !startLng || !endLat || !endLng) { if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Coordinate di partenza e arrivo richieste' message: 'Coordinate di partenza e arrivo richieste',
}); });
} }
// Costruisci stringa coordinate // Costruisci array coordinate [lng, lat] (formato GeoJSON)
let coordinates = `${startLng},${startLat}`; const coordinates = [[parseFloat(startLng), parseFloat(startLat)]];
if (waypoints) { if (waypoints) {
const waypointsList = waypoints.split(';'); const waypointsList = waypoints.split(';');
waypointsList.forEach(wp => { waypointsList.forEach((wp) => {
const [lat, lng] = wp.split(','); const [lat, lng] = wp.split(',').map(parseFloat);
coordinates += `;${lng},${lat}`; coordinates.push([lng, lat]);
}); });
} }
coordinates += `;${endLng},${endLat}`; coordinates.push([parseFloat(endLng), parseFloat(endLat)]);
const url = `${OSRM_BASE}/route/v1/driving/${coordinates}?overview=full&geometries=polyline&steps=true`; // Richiesta POST a ORS Directions
const url = `${ORS_BASE}/v2/directions/${profile}`;
const data = await makeRequest(url); const body = {
coordinates,
language,
units,
geometry: true,
instructions: true,
maneuvers: true,
};
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({ return res.status(404).json({
success: false, success: false,
message: 'Impossibile calcolare il percorso' message: 'Impossibile calcolare il percorso',
}); });
} }
const route = data.routes[0]; const route = data.routes[0];
const summary = route.summary;
// 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 = { const result = {
distance: Math.round(route.distance / 1000 * 10) / 10, // km distance: Math.round(summary.distance * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti duration: Math.round(summary.duration / 60), // minuti
polyline: route.geometry, // Polyline encoded durationFormatted: formatDuration(summary.duration),
legs: route.legs.map(leg => ({ bbox: data.bbox, // Bounding box
distance: Math.round(leg.distance / 1000 * 10) / 10, geometry: route.geometry, // Polyline encoded
duration: Math.round(leg.duration / 60), segments: route.segments.map((segment) => ({
summary: leg.summary, distance: Math.round(segment.distance * 10) / 10,
steps: leg.steps ? leg.steps.slice(0, 10).map(s => ({ // Limita step duration: Math.round(segment.duration / 60),
instruction: s.maneuver ? s.maneuver.instruction : '', steps: segment.steps.map((step) => ({
name: s.name, instruction: step.instruction,
distance: Math.round(s.distance), name: step.name,
duration: Math.round(s.duration / 60) distance: Math.round(step.distance * 100) / 100,
})) : [] duration: Math.round(step.duration / 60),
})) type: step.type,
maneuver: step.maneuver,
})),
})),
}; };
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: result data: result,
}); });
} catch (error) { } catch (error) {
console.error('Errore calcolo percorso:', error); console.error('Errore calcolo percorso:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante il calcolo del percorso', 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 * @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) => { const suggestWaypoints = async (req, res) => {
try { try {
const { startLat, startLng, endLat, endLng } = req.query; const { startLat, startLng, endLat, endLng, count = 3 } = req.query;
if (!startLat || !startLng || !endLat || !endLng) { if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Coordinate di partenza e arrivo richieste' message: 'Coordinate di partenza e arrivo richieste',
}); });
} }
// Prima ottieni il percorso // 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({ return res.status(404).json({
success: false, success: false,
message: 'Impossibile calcolare il percorso' message: 'Impossibile calcolare il percorso',
}); });
} }
// Prendi punti lungo il percorso (ogni ~50km circa) // Decodifica polyline per ottenere punti
const coordinates = routeData.routes[0].geometry.coordinates; const geometry = routeData.routes[0].geometry;
const totalPoints = coordinates.length; const decodedPoints = decodePolyline(geometry);
const step = Math.max(1, Math.floor(totalPoints / 6)); // ~5 punti intermedi
// Seleziona punti equidistanti lungo il percorso
const totalPoints = decodedPoints.length;
const step = Math.floor(totalPoints / (parseInt(count) + 1));
const sampledPoints = []; const sampledPoints = [];
for (let i = step; i < totalPoints - step; i += step) { for (let i = 1; i <= count; i++) {
sampledPoints.push(coordinates[i]); const index = Math.min(step * i, totalPoints - 1);
sampledPoints.push(decodedPoints[index]);
} }
// Fai reverse geocoding per ogni punto // Fai reverse geocoding per ogni punto
const cities = []; const cities = [];
const seenCities = new Set(); const seenCities = new Set();
for (const point of sampledPoints.slice(0, 5)) { // Limita a 5 richieste for (const point of sampledPoints) {
try { 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); const data = await makeRequest(reverseUrl);
if (data && data.address) { if (data.features && data.features.length > 0) {
const cityName = data.address.city || data.address.town || data.address.village; const feature = data.features[0];
const cityName = feature.properties.locality || feature.properties.county;
if (cityName && !seenCities.has(cityName.toLowerCase())) { if (cityName && !seenCities.has(cityName.toLowerCase())) {
seenCities.add(cityName.toLowerCase()); seenCities.add(cityName.toLowerCase());
cities.push({ cities.push({
city: cityName, city: cityName,
province: data.address.county || data.address.province, county: feature.properties.county,
region: data.address.state, region: feature.properties.region,
coordinates: { coordinates: {
lat: point[1], 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) { } catch (e) {
console.log('Errore reverse per punto:', e.message); console.log('Errore reverse per punto:', e.message);
} }
@@ -380,124 +482,192 @@ const suggestWaypoints = async (req, res) => {
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: cities count: cities.length,
data: cities,
}); });
} catch (error) { } catch (error) {
console.error('Errore suggerimento waypoints:', error); console.error('Errore suggerimento waypoints:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante il suggerimento delle tappe', message: 'Errore durante il suggerimento delle tappe',
error: error.message error: error.message,
}); });
} }
}; };
/** /**
* @desc Cerca città italiane (ottimizzato per Italia) * @desc Cerca città italiane (ottimizzato)
* @route GET /api/trasporti/geo/cities/it * @route GET /api/geo/cities/it
*/ */
const searchItalianCities = async (req, res) => { const searchItalianCities = async (req, res) => {
try { try {
const { q, limit = 10 } = req.query; const { q, limit = 10, region } = req.query;
if (!q || q.length < 2) { if (!q || q.length < 2) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: 'Query deve essere almeno 2 caratteri' message: 'Query deve essere almeno 2 caratteri',
}); });
} }
// Usa Nominatim con filtro Italia const params = new URLSearchParams({
const url = `${NOMINATIM_BASE}/search?format=json&q=${encodeURIComponent(q)}&countrycodes=it&limit=${limit}&addressdetails=1&featuretype=city`; 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 data = await makeRequest(url);
const results = data const results = data.features
.filter(item => .filter((f) => f.properties.locality || f.properties.county)
item.address && .map((feature) => ({
(item.address.city || item.address.town || item.address.village) city: feature.properties.locality || feature.properties.name,
) county: feature.properties.county,
.map(item => ({ region: feature.properties.region,
city: item.address.city || item.address.town || item.address.village, postalCode: feature.properties.postalcode,
province: item.address.county || item.address.province,
region: item.address.state,
postalCode: item.address.postcode,
coordinates: { coordinates: {
lat: parseFloat(item.lat), lat: feature.geometry.coordinates[1],
lng: parseFloat(item.lon) 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 // Rimuovi duplicati
const unique = results.filter((v, i, a) => const unique = results.filter(
a.findIndex(t => t.city.toLowerCase() === v.city.toLowerCase()) === i (v, i, a) => a.findIndex((t) => t.city?.toLowerCase() === v.city?.toLowerCase()) === i
); );
res.status(200).json({ res.status(200).json({
success: true, success: true,
data: unique count: unique.length,
data: unique,
}); });
} catch (error) { } catch (error) {
console.error('Errore ricerca città italiane:', error); console.error('Errore ricerca città italiane:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante la ricerca', message: 'Errore durante la ricerca',
error: error.message error: error.message,
}); });
} }
}; };
/** /**
* @desc Calcola distanza e durata tra due punti * @desc Calcola distanza e durata tra due punti (semplificato)
* @route GET /api/trasporti/geo/distance * @route GET /api/geo/distance
*/ */
const getDistance = async (req, res) => { const getDistance = async (req, res) => {
try { try {
const { startLat, startLng, endLat, endLng } = req.query; const { startLat, startLng, endLat, endLng, profile = 'driving-car' } = req.query;
if (!startLat || !startLng || !endLat || !endLng) { if (!startLat || !startLng || !endLat || !endLng) {
return res.status(400).json({ return res.status(400).json({
success: false, 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({ return res.status(404).json({
success: false, 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({ res.status(200).json({
success: true, success: true,
data: { data: {
distance: Math.round(route.distance / 1000 * 10) / 10, // km distance: Math.round(summary.distance * 10) / 10, // km
duration: Math.round(route.duration / 60), // minuti duration: Math.round(summary.duration / 60), // minuti
durationFormatted: formatDuration(route.duration) durationFormatted: formatDuration(summary.duration),
} profile,
},
}); });
} catch (error) { } catch (error) {
console.error('Errore calcolo distanza:', error); console.error('Errore calcolo distanza:', error);
res.status(500).json({ res.status(500).json({
success: false, success: false,
message: 'Errore durante il calcolo della distanza', 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 formatDuration = (seconds) => {
const hours = Math.floor(seconds / 3600); const hours = Math.floor(seconds / 3600);
const minutes = Math.round((seconds % 3600) / 60); const minutes = Math.round((seconds % 3600) / 60);
@@ -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 = { module.exports = {
autocomplete, autocomplete,
geocode, geocode,
reverseGeocode, reverseGeocode,
getRoute, getRoute,
getMatrix,
suggestWaypoints, suggestWaypoints,
searchItalianCities, 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 Ride = require('../models/Ride');
const User = require('../models/user'); const { User } = require('../models/user');
const RideRequest = require('../models/RideRequest'); const RideRequest = require('../models/RideRequest');
/** /**
* @desc Crea un nuovo viaggio (offerta o richiesta) * @desc Crea un nuovo viaggio (offerta o richiesta)
* @route POST /api/trasporti/rides * @route POST /api/viaggi/rides
* @access Private * @access Private
*/ */
const createRide = async (req, res) => { const createRide = async (req, res) => {
try { try {
const { idapp } = req.body; const idapp = req.user.idapp;
const userId = req.user._id; const userId = req.user._id;
const { const {
@@ -111,9 +111,7 @@ const createRide = async (req, res) => {
// Aggiorna profilo utente come driver se è un'offerta // Aggiorna profilo utente come driver se è un'offerta
if (type === 'offer') { if (type === 'offer') {
await User.findByIdAndUpdate(userId, { await User.findByIdAndUpdate(userId, { $set: { 'profile.driverProfile.isDriver': true } }, { new: true });
'profile.driverProfile.isDriver': true
});
} }
// Popola i dati per la risposta // Popola i dati per la risposta
@@ -137,12 +135,12 @@ const createRide = async (req, res) => {
/** /**
* @desc Ottieni lista viaggi con filtri * @desc Ottieni lista viaggi con filtri
* @route GET /api/trasporti/rides * @route GET /api/viaggi/rides
* @access Public * @access Public
*/ */
const getRides = async (req, res) => { const getRides = async (req, res) => {
try { try {
const { idapp } = req.query; const idapp = req.query.idapp;
const { const {
type, type,
@@ -272,15 +270,14 @@ const getRides = async (req, res) => {
/** /**
* @desc Ottieni singolo viaggio per ID * @desc Ottieni singolo viaggio per ID
* @route GET /api/trasporti/rides/:id * @route GET /api/viaggi/rides/:id
* @access Public * @access Public
*/ */
const getRideById = async (req, res) => { const getRideById = async (req, res) => {
try { try {
const { id } = req.params; 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('userId', 'username name surname profile.img profile.Biografia profile.driverProfile')
.populate('confirmedPassengers.userId', 'username name surname profile.img') .populate('confirmedPassengers.userId', 'username name surname profile.img')
.populate('contribution.contribTypes.contribTypeId'); .populate('contribution.contribTypes.contribTypeId');
@@ -313,14 +310,15 @@ const getRideById = async (req, res) => {
/** /**
* @desc Aggiorna un viaggio * @desc Aggiorna un viaggio
* @route PUT /api/trasporti/rides/:id * @route PUT /api/viaggi/rides/:id
* @access Private * @access Private
*/ */
const updateRide = async (req, res) => { const updateRide = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.user._id; const userId = req.user._id;
const { idapp, ...updateData } = req.body; const idapp = req.user.idapp;
const { ...updateData } = req.body;
// Trova il viaggio // Trova il viaggio
const ride = await Ride.findOne({ _id: id, idapp }); const ride = await Ride.findOne({ _id: id, idapp });
@@ -333,7 +331,7 @@ const updateRide = async (req, res) => {
} }
// Verifica proprietario // Verifica proprietario
if (ride.userId.toString() !== userId) { if (!ride.userId.equals(userId)) {
return res.status(403).json({ return res.status(403).json({
success: false, success: false,
message: 'Non sei autorizzato a modificare questo viaggio' message: 'Non sei autorizzato a modificare questo viaggio'
@@ -400,14 +398,15 @@ const updateRide = async (req, res) => {
/** /**
* @desc Cancella un viaggio * @desc Cancella un viaggio
* @route DELETE /api/trasporti/rides/:id * @route DELETE /api/viaggi/rides/:id
* @access Private * @access Private
*/ */
const deleteRide = async (req, res) => { const deleteRide = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.user._id; 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 }); const ride = await Ride.findOne({ _id: id, idapp });
@@ -419,7 +418,7 @@ const deleteRide = async (req, res) => {
} }
// Verifica proprietario // Verifica proprietario
if (ride.userId.toString() !== userId) { if (!ride.userId.equals(userId)) {
return res.status(403).json({ return res.status(403).json({
success: false, success: false,
message: 'Non sei autorizzato a cancellare questo viaggio' message: 'Non sei autorizzato a cancellare questo viaggio'
@@ -463,13 +462,14 @@ const deleteRide = async (req, res) => {
/** /**
* @desc Ottieni viaggi dell'utente corrente * @desc Ottieni viaggi dell'utente corrente
* @route GET /api/trasporti/rides/my * @route GET /api/viaggi/rides/my
* @access Private * @access Private
*/ */
const getMyRides = async (req, res) => { const getMyRides = async (req, res) => {
try { try {
const userId = req.user._id; 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 }; let query = { idapp };
@@ -539,12 +539,12 @@ const getMyRides = async (req, res) => {
/** /**
* @desc Cerca viaggi con match intelligente * @desc Cerca viaggi con match intelligente
* @route GET /api/trasporti/rides/search * @route GET /api/viaggi/rides/search
* @access Public * @access Public
*/ */
const searchRides = async (req, res) => { const searchRides = async (req, res) => {
try { try {
const { idapp } = req.query; const idapp = req.query.idapp;
const { const {
from, from,
@@ -659,14 +659,14 @@ const searchRides = async (req, res) => {
/** /**
* @desc Completa un viaggio * @desc Completa un viaggio
* @route POST /api/trasporti/rides/:id/complete * @route POST /api/viaggi/rides/:id/complete
* @access Private * @access Private
*/ */
const completeRide = async (req, res) => { const completeRide = async (req, res) => {
try { try {
const { id } = req.params; const { id } = req.params;
const userId = req.user._id; const userId = req.user._id;
const { idapp } = req.body; const idapp = req.user.idapp;
const ride = await Ride.findOne({ _id: id, 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({ return res.status(403).json({
success: false, success: false,
message: 'Solo il conducente può completare il viaggio' message: 'Solo il conducente può completare il viaggio'
@@ -723,12 +723,12 @@ const completeRide = async (req, res) => {
/** /**
* @desc Ottieni statistiche viaggi per homepage widget * @desc Ottieni statistiche viaggi per homepage widget
* @route GET /api/trasporti/rides/stats * @route GET /api/viaggi/rides/stats
* @access Private * @access Private
*/ */
const getRidesStats = async (req, res) => { const getRidesStats = async (req, res) => {
try { try {
const { idapp } = req.query; const idapp = req.query.idapp;
const userId = req.user._id; const userId = req.user._id;
const now = new Date(); const now = new Date();
@@ -800,7 +800,7 @@ const getRidesStats = async (req, res) => {
/** /**
* Get aggregated data for dashboard widgets * Get aggregated data for dashboard widgets
* GET /api/trasporti/widget/data * GET /api/viaggi/widget/data
*/ */
const getWidgetData = async (req, res) => { const getWidgetData = async (req, res) => {
try { try {
@@ -981,7 +981,7 @@ const getWidgetData = async (req, res) => {
/** /**
* Get comprehensive statistics summary for user * Get comprehensive statistics summary for user
* GET /api/trasporti/stats/summary * GET /api/viaggi/stats/summary
*/ */
const getStatsSummary = async (req, res) => { const getStatsSummary = async (req, res) => {
try { try {
@@ -1420,7 +1420,7 @@ const getStatsSummary = async (req, res) => {
/** /**
* Get city suggestions for autocomplete * Get city suggestions for autocomplete
* GET /api/trasporti/cities/suggestions?q=query * GET /api/viaggi/cities/suggestions?q=query
*/ */
const getCitySuggestions = async (req, res) => { const getCitySuggestions = async (req, res) => {
try { try {
@@ -1679,7 +1679,7 @@ const getCitySuggestions = async (req, res) => {
/** /**
* Get recent cities from user's trip history * Get recent cities from user's trip history
* GET /api/trasporti/cities/recent * GET /api/viaggi/cities/recent
*/ */
const getRecentCities = async (req, res) => { const getRecentCities = async (req, res) => {
try { try {

View File

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

View File

@@ -79,6 +79,16 @@ const ChatSchema = new Schema(
ref: 'User', ref: 'User',
}, },
], ],
deletedBy: [
{
type: Schema.Types.ObjectId,
ref: 'User',
},
],
clearedBefore: {
type: Map,
of: Date,
},
metadata: { metadata: {
type: Schema.Types.Mixed, type: Schema.Types.Mixed,
}, },
@@ -117,9 +127,7 @@ ChatSchema.methods.incrementUnread = function (excludeUserId) {
this.participants.forEach((participantId) => { this.participants.forEach((participantId) => {
// Gestisci sia ObjectId che oggetti popolati // Gestisci sia ObjectId che oggetti popolati
const id = participantId._id const id = participantId._id ? participantId._id.toString() : participantId.toString();
? participantId._id.toString()
: participantId.toString();
if (id !== excludeIdStr) { if (id !== excludeIdStr) {
const current = this.unreadCount.get(id) || 0; const current = this.unreadCount.get(id) || 0;
@@ -171,7 +179,6 @@ ChatSchema.methods.isBlockedFor = function (userId) {
}); });
}; };
// 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

View File

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

View File

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

View File

@@ -13,7 +13,6 @@ const PageView = require('../models/PageView');
const fal = require('@fal-ai/client'); const fal = require('@fal-ai/client');
const imageGenerator = require('../services/imageGenerator'); // Assicurati che il percorso sia corretto const imageGenerator = require('../services/imageGenerator'); // Assicurati che il percorso sia corretto
const posterEditor = require('../services/PosterEditor'); // <--- Importa la nuova classe const posterEditor = require('../services/PosterEditor'); // <--- Importa la nuova classe
@@ -33,9 +32,8 @@ const { MyElem } = require('../models/myelem');
const axios = require('axios'); const axios = require('axios');
const trasportiRoutes = require('../routes/trasportiRoutes'); const viaggiRoutes = require('../routes/viaggiRoutes');
router.use('/trasporti', trasportiRoutes); router.use('/viaggi', viaggiRoutes);
// Importa le routes video // Importa le routes video
const videoRoutes = require('../routes/videoRoutes'); const videoRoutes = require('../routes/videoRoutes');
@@ -43,7 +41,6 @@ const videoRoutes = require('../routes/videoRoutes');
// Monta le routes video // Monta le routes video
router.use('/video', videoRoutes); router.use('/video', videoRoutes);
router.use('/templates', authenticate, templatesRouter); router.use('/templates', authenticate, templatesRouter);
router.use('/posters', authenticate, postersRouter); router.use('/posters', authenticate, postersRouter);
router.use('/assets', authenticate, assetsRouter); router.use('/assets', authenticate, assetsRouter);
@@ -524,8 +521,15 @@ router.post('/chatbot', authenticate, async (req, res) => {
router.post('/generateposter', async (req, res) => { router.post('/generateposter', async (req, res) => {
const { const {
titolo, data, ora, luogo, descrizione, contatti, fotoDescrizione, stile, titolo,
provider = 'hf' // Default a HF (Gratis) data,
ora,
luogo,
descrizione,
contatti,
fotoDescrizione,
stile,
provider = 'hf', // Default a HF (Gratis)
} = req.body; } = req.body;
// 1. Prompt per l'AI: Chiediamo SOLO lo sfondo, VIETIAMO il testo. // 1. Prompt per l'AI: Chiediamo SOLO lo sfondo, VIETIAMO il testo.
@@ -547,21 +551,50 @@ router.post('/generateposter', async (req, res) => {
data, data,
ora, ora,
luogo, luogo,
contatti contatti,
}); });
res.json({ res.json({
success: true, success: true,
imageUrl: finalPosterBase64, // Restituisce l'immagine completa in base64 imageUrl: finalPosterBase64, // Restituisce l'immagine completa in base64
step: 'AI + Canvas Composition' step: 'AI + Canvas Composition',
}); });
} catch (err) { } catch (err) {
console.error('Errore:', err.message); console.error('Errore:', err.message);
res.status(500).json({ error: 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; module.exports = router;

View File

@@ -317,7 +317,11 @@ router.post('/', async (req, res) => {
await telegrambot.askConfirmationUser(myuser.idapp, shared_consts.CallFunz.REGISTRATION, myuser); await telegrambot.askConfirmationUser(myuser.idapp, shared_consts.CallFunz.REGISTRATION, myuser);
const { token, refreshToken, browser_random } = await myuser.generateAuthToken(req, browser_random); 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; return true;
} }
} }
@@ -368,7 +372,11 @@ router.post('/', async (req, res) => {
// if (!tools.testing()) { // if (!tools.testing()) {
await sendemail.sendEmail_Registration(user.lang, user.email, user, user.idapp, user.linkreg); 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; return true;
}); });
}) })
@@ -411,7 +419,9 @@ router.patch('/:id', authenticate, (req, res) => {
if (!User.isAdmin(req.user.perm)) { if (!User.isAdmin(req.user.perm)) {
// If without permissions, exit // 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 }) User.findByIdAndUpdate(id, { $set: body })
@@ -591,9 +601,14 @@ router.post('/panel', authenticate, async (req, res) => {
idapp = req.body.idapp; idapp = req.body.idapp;
locale = req.body.locale; 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 // 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 { try {
@@ -953,7 +968,9 @@ router.post('/friends/cmd', authenticate, async (req, res) => {
usernameDest !== usernameLogged && usernameDest !== usernameLogged &&
(cmd === shared_consts.FRIENDSCMD.SETFRIEND || cmd === shared_consts.FRIENDSCMD.SETHANDSHAKE) (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') { } else if (mydata.dbop === 'noNameSurname') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noNameSurname': mydata.value } }); await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noNameSurname': mydata.value } });
} else if (mydata.dbop === 'telegram_verification_skipped') { } 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') { } else if (mydata.dbop === 'pwdLikeAdmin') {
await User.setPwdComeQuellaDellAdmin(mydata); await User.setPwdComeQuellaDellAdmin(mydata);
} else if (mydata.dbop === 'ripristinaPwdPrec') { } else if (mydata.dbop === 'ripristinaPwdPrec') {
@@ -1129,7 +1149,7 @@ async function eseguiDbOpUser(idapp, mydata, locale, req, res) {
} else if (mydata.dbop === 'noComune') { } else if (mydata.dbop === 'noComune') {
await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noComune': mydata.value } }); await User.findOneAndUpdate({ _id: mydata._id }, { $set: { 'profile.noComune': mydata.value } });
} else if (mydata.dbop === 'verifiedemail') { } 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') { } else if (mydata.dbop === 'resendVerificationEmail') {
// Invia la email di Verifica email // Invia la email di Verifica email
const ris = await sendemail.sendEmail_ReVerifyingEmail(mydata, idapp); const ris = await sendemail.sendEmail_ReVerifyingEmail(mydata, idapp);

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

View File

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