400 lines
15 KiB
JavaScript
400 lines
15 KiB
JavaScript
const nocodbService = require('../services/nocodb');
|
|
const logger = require('../utils/logger');
|
|
const config = require('../config');
|
|
const listmonkService = require('../services/listmonk');
|
|
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`);
|
|
|
|
// Check if user is temp user and limit data accordingly
|
|
if (req.session?.userType === 'temp') {
|
|
// For temp users, return limited data but include necessary fields for functionality
|
|
const limitedLocations = validLocations.map(loc => {
|
|
const locationId = loc.id || loc.Id || loc.ID || loc._id;
|
|
return {
|
|
// Include ID with all possible variants for compatibility
|
|
id: locationId,
|
|
Id: locationId,
|
|
ID: locationId,
|
|
_id: locationId,
|
|
'Geo-Location': loc['Geo-Location'],
|
|
latitude: loc.latitude,
|
|
longitude: loc.longitude,
|
|
// Include display fields needed for map functionality
|
|
'First Name': loc['First Name'] || '',
|
|
'Last Name': loc['Last Name'] || '', // Include last name for display
|
|
'Support Level': loc['Support Level'],
|
|
Address: loc.Address || '',
|
|
'Unit Number': loc['Unit Number'] || '',
|
|
Notes: loc.Notes || '',
|
|
Sign: loc.Sign,
|
|
'Sign Size': loc['Sign Size'] || '',
|
|
// Exclude sensitive fields like Email, Phone
|
|
};
|
|
});
|
|
|
|
logger.info(`Returning limited data for temp user: ${limitedLocations.length} locations`);
|
|
|
|
return res.json({
|
|
success: true,
|
|
count: limitedLocations.length,
|
|
total: response.pageInfo?.totalRows || limitedLocations.length,
|
|
locations: limitedLocations,
|
|
isLimited: true // Flag to indicate limited data
|
|
});
|
|
}
|
|
|
|
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 {
|
|
// Add debugging logs
|
|
logger.info('Session data:', {
|
|
authenticated: req.session.authenticated,
|
|
userId: req.session.userId,
|
|
userEmail: req.session.userEmail,
|
|
isAdmin: req.session.isAdmin
|
|
});
|
|
|
|
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) {
|
|
const boundsCheck = checkBounds(validation.latitude, validation.longitude);
|
|
if (!boundsCheck.valid) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: boundsCheck.error
|
|
});
|
|
}
|
|
}
|
|
|
|
// 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_by_user: req.session.userEmail || 'anonymous' // Add fallback
|
|
};
|
|
|
|
logger.info('Final data being sent to NocoDB:', finalData);
|
|
|
|
logger.info('Creating new location:', {
|
|
lat: validation.latitude,
|
|
lng: validation.longitude,
|
|
user: req.session.userEmail
|
|
});
|
|
|
|
const response = await nocodbService.create(
|
|
config.nocodb.tableId,
|
|
finalData
|
|
);
|
|
|
|
logger.info('Location created successfully:', extractId(response));
|
|
|
|
// Real-time sync to Listmonk (async, don't block response)
|
|
if (listmonkService.syncEnabled && response.Email) {
|
|
setImmediate(async () => {
|
|
try {
|
|
const syncResult = await listmonkService.syncLocation(response);
|
|
if (!syncResult.success) {
|
|
logger.warn('Listmonk sync failed for new location', {
|
|
locationId: extractId(response),
|
|
email: response.Email,
|
|
error: syncResult.error
|
|
});
|
|
} else {
|
|
logger.debug('Location synced to Listmonk', {
|
|
locationId: extractId(response),
|
|
email: response.Email
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error('Listmonk sync error for new location', {
|
|
locationId: extractId(response),
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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);
|
|
|
|
// Add update tracking
|
|
updateData.last_updated_by_user = req.session.userEmail; // Changed from last_updated_by
|
|
|
|
logger.info(`Updating location ${locationId}`, {
|
|
user: req.session.userEmail
|
|
});
|
|
|
|
const response = await nocodbService.update(
|
|
config.nocodb.tableId,
|
|
locationId,
|
|
updateData
|
|
);
|
|
|
|
logger.info('Location updated successfully:', locationId);
|
|
|
|
// Real-time sync to Listmonk (async, don't block response)
|
|
if (listmonkService.syncEnabled && response.Email) {
|
|
setImmediate(async () => {
|
|
try {
|
|
const syncResult = await listmonkService.syncLocation(response);
|
|
if (!syncResult.success) {
|
|
logger.warn('Listmonk sync failed for updated location', {
|
|
locationId: locationId,
|
|
email: response.Email,
|
|
error: syncResult.error
|
|
});
|
|
} else {
|
|
logger.debug('Updated location synced to Listmonk', {
|
|
locationId: locationId,
|
|
email: response.Email
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error('Listmonk sync error for updated location', {
|
|
locationId: locationId,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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'
|
|
});
|
|
}
|
|
|
|
// Get location data before deletion (for Listmonk cleanup)
|
|
let locationData = null;
|
|
if (listmonkService.syncEnabled) {
|
|
try {
|
|
const getResponse = await nocodbService.getById(config.nocodb.tableId, locationId);
|
|
locationData = getResponse;
|
|
} catch (error) {
|
|
logger.warn('Could not fetch location data before deletion', error.message);
|
|
}
|
|
}
|
|
|
|
await nocodbService.delete(
|
|
config.nocodb.tableId,
|
|
locationId
|
|
);
|
|
|
|
logger.info(`Location ${locationId} deleted by ${req.session.userEmail}`);
|
|
|
|
// Remove from Listmonk (async, don't block response)
|
|
if (listmonkService.syncEnabled && locationData && locationData.Email) {
|
|
setImmediate(async () => {
|
|
try {
|
|
const syncResult = await listmonkService.removeSubscriber(locationData.Email);
|
|
if (!syncResult.success) {
|
|
logger.warn('Failed to remove deleted location from Listmonk', {
|
|
locationId: locationId,
|
|
email: locationData.Email,
|
|
error: syncResult.error
|
|
});
|
|
} else {
|
|
logger.debug('Deleted location removed from Listmonk', {
|
|
locationId: locationId,
|
|
email: locationData.Email
|
|
});
|
|
}
|
|
} catch (error) {
|
|
logger.error('Listmonk cleanup error for deleted location', {
|
|
locationId: locationId,
|
|
error: error.message
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
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(); |