const nocodbService = require('../services/nocodb'); const logger = require('../utils/logger'); const config = require('../config'); const { syncGeoFields, validateCoordinates, checkBounds, extractId } = require('../utils/helpers'); class LocationsController { async getAll(req, res) { try { const { limit = 1000, offset = 0, where } = req.query; const params = { limit, offset }; if (where) params.where = where; logger.info('Fetching locations from NocoDB'); const response = await nocodbService.getLocations(params); const locations = response.list || []; // Process and validate locations const validLocations = locations.filter(loc => { loc = syncGeoFields(loc); if (loc.latitude && loc.longitude) { return true; } // Try to parse from geodata column if (loc.geodata && typeof loc.geodata === 'string') { const parts = loc.geodata.split(';'); if (parts.length === 2) { loc.latitude = parseFloat(parts[0]); loc.longitude = parseFloat(parts[1]); return !isNaN(loc.latitude) && !isNaN(loc.longitude); } } return false; }); logger.info(`Retrieved ${validLocations.length} valid locations out of ${locations.length} total`); res.json({ success: true, count: validLocations.length, total: response.pageInfo?.totalRows || validLocations.length, locations: validLocations }); } catch (error) { logger.error('Error fetching locations:', error.message); if (error.response) { res.status(error.response.status).json({ success: false, error: 'Failed to fetch data from NocoDB', details: error.response.data }); } else if (error.code === 'ECONNABORTED') { res.status(504).json({ success: false, error: 'Request timeout' }); } else { res.status(500).json({ success: false, error: 'Internal server error' }); } } } async getById(req, res) { try { const location = await nocodbService.getById( config.nocodb.tableId, req.params.id ); res.json({ success: true, location }); } catch (error) { logger.error(`Error fetching location ${req.params.id}:`, error.message); res.status(error.response?.status || 500).json({ success: false, error: 'Failed to fetch location' }); } } async create(req, res) { try { let locationData = { ...req.body }; locationData = syncGeoFields(locationData); const { latitude, longitude, ...additionalData } = locationData; // Validate coordinates const validation = validateCoordinates(latitude, longitude); if (!validation.valid) { return res.status(400).json({ success: false, error: validation.error }); } // Check bounds if configured if (config.map.bounds) { if (!checkBounds(validation.latitude, validation.longitude, config.map.bounds)) { return res.status(400).json({ success: false, error: 'Location is outside allowed bounds' }); } } // Format geodata with string values to preserve precision const geodata = `${validation.latitude};${validation.longitude}`; // Prepare data for NocoDB - keep coordinates as strings const finalData = { geodata, 'Geo-Location': geodata, latitude: validation.latitude, longitude: validation.longitude, ...additionalData, created_at: new Date().toISOString(), created_by: req.session.userEmail }; logger.info('Creating new location:', { lat: validation.latitude, lng: validation.longitude }); const response = await nocodbService.create( config.nocodb.tableId, finalData ); logger.info('Location created successfully:', extractId(response)); res.status(201).json({ success: true, location: response }); } catch (error) { logger.error('Error creating location:', error.message); if (error.response) { res.status(error.response.status).json({ success: false, error: 'Failed to save location to NocoDB', details: error.response.data }); } else { res.status(500).json({ success: false, error: 'Internal server error' }); } } } async update(req, res) { try { const locationId = req.params.id; // Validate ID if (!locationId || locationId === 'undefined' || locationId === 'null') { return res.status(400).json({ success: false, error: 'Invalid location ID' }); } let updateData = { ...req.body }; // Remove ID fields to avoid conflicts delete updateData.ID; delete updateData.Id; delete updateData.id; delete updateData._id; // Sync geo fields updateData = syncGeoFields(updateData); updateData.last_updated_at = new Date().toISOString(); updateData.last_updated_by = req.session.userEmail; logger.info(`Updating location ${locationId} by ${req.session.userEmail}`); const response = await nocodbService.update( config.nocodb.tableId, locationId, updateData ); res.json({ success: true, location: response }); } catch (error) { logger.error(`Error updating location ${req.params.id}:`, error.message); res.status(error.response?.status || 500).json({ success: false, error: 'Failed to update location', details: error.response?.data?.message || error.message }); } } async delete(req, res) { try { const locationId = req.params.id; // Validate ID if (!locationId || locationId === 'undefined' || locationId === 'null') { return res.status(400).json({ success: false, error: 'Invalid location ID' }); } await nocodbService.delete( config.nocodb.tableId, locationId ); logger.info(`Location ${locationId} deleted by ${req.session.userEmail}`); res.json({ success: true, message: 'Location deleted successfully' }); } catch (error) { logger.error(`Error deleting location ${req.params.id}:`, error.message); res.status(error.response?.status || 500).json({ success: false, error: 'Failed to delete location' }); } } } module.exports = new LocationsController();