Files
rentall-app/backend/routes/maps.js
2025-09-09 22:49:55 -04:00

199 lines
4.7 KiB
JavaScript

const express = require("express");
const router = express.Router();
const { authenticateToken } = require("../middleware/auth");
const rateLimiter = require("../middleware/rateLimiter");
const googleMapsService = require("../services/googleMapsService");
// Input validation middleware
const validateInput = (req, res, next) => {
// Basic input sanitization
if (req.body.input) {
req.body.input = req.body.input.toString().trim();
// Prevent extremely long inputs
if (req.body.input.length > 500) {
return res.status(400).json({ error: "Input too long" });
}
}
if (req.body.placeId) {
req.body.placeId = req.body.placeId.toString().trim();
// Basic place ID validation
if (!/^[A-Za-z0-9_-]+$/.test(req.body.placeId)) {
return res.status(400).json({ error: "Invalid place ID format" });
}
}
if (req.body.address) {
req.body.address = req.body.address.toString().trim();
if (req.body.address.length > 500) {
return res.status(400).json({ error: "Address too long" });
}
}
next();
};
// Error handling middleware
const handleServiceError = (error, res) => {
console.error("Maps service error:", error.message);
if (error.message.includes("API key not configured")) {
return res.status(503).json({
error: "Maps service temporarily unavailable",
details: "Configuration issue",
});
}
if (error.message.includes("quota exceeded")) {
return res.status(429).json({
error: "Service temporarily unavailable due to high demand",
details: "Please try again later",
});
}
return res.status(500).json({
error: "Failed to process request",
details: error.message,
});
};
/**
* POST /api/maps/places/autocomplete
* Proxy for Google Places Autocomplete API
*/
router.post(
"/places/autocomplete",
authenticateToken,
rateLimiter.burstProtection,
rateLimiter.placesAutocomplete,
validateInput,
async (req, res) => {
try {
const { input, types, componentRestrictions, sessionToken } = req.body;
if (!input || input.length < 2) {
return res.json({ predictions: [] });
}
const options = {
types: types || ["address"],
componentRestrictions,
sessionToken,
};
const result = await googleMapsService.getPlacesAutocomplete(
input,
options
);
// Log request for monitoring (without sensitive data)
console.log(
`Places Autocomplete: user=${
req.user?.id || "anonymous"
}, query_length=${input.length}, results=${
result.predictions?.length || 0
}`
);
res.json(result);
} catch (error) {
handleServiceError(error, res);
}
}
);
/**
* POST /api/maps/places/details
* Proxy for Google Places Details API
*/
router.post(
"/places/details",
authenticateToken,
rateLimiter.burstProtection,
rateLimiter.placeDetails,
validateInput,
async (req, res) => {
try {
const { placeId, sessionToken } = req.body;
if (!placeId) {
return res.status(400).json({ error: "Place ID is required" });
}
const options = {
sessionToken,
};
const result = await googleMapsService.getPlaceDetails(placeId, options);
// Log request for monitoring
console.log(
`Place Details: user=${
req.user?.id || "anonymous"
}, placeId=${placeId.substring(0, 10)}...`
);
res.json(result);
} catch (error) {
handleServiceError(error, res);
}
}
);
/**
* POST /api/maps/geocode
* Proxy for Google Geocoding API
*/
router.post(
"/geocode",
authenticateToken,
rateLimiter.burstProtection,
rateLimiter.geocoding,
validateInput,
async (req, res) => {
try {
const { address, componentRestrictions } = req.body;
if (!address) {
return res.status(400).json({ error: "Address is required" });
}
const options = {
componentRestrictions,
};
const result = await googleMapsService.geocodeAddress(address, options);
// Log request for monitoring
console.log(
`Geocoding: user=${req.user?.id || "anonymous"}, address_length=${
address.length
}`
);
res.json(result);
} catch (error) {
handleServiceError(error, res);
}
}
);
/**
* GET /api/maps/health
* Health check endpoint for Maps service
*/
router.get("/health", (req, res) => {
const isConfigured = googleMapsService.isConfigured();
res.status(isConfigured ? 200 : 503).json({
status: isConfigured ? "healthy" : "unavailable",
service: "Google Maps API Proxy",
timestamp: new Date().toISOString(),
configuration: {
apiKeyConfigured: isConfigured,
},
});
});
module.exports = router;