202 lines
7.3 KiB
JavaScript
202 lines
7.3 KiB
JavaScript
// Sync geographic fields between different formats
|
|
function syncGeoFields(data) {
|
|
// If we have latitude and longitude but no Geo-Location, create it
|
|
if (data.latitude && data.longitude && !data['Geo-Location']) {
|
|
// Keep as strings to preserve precision
|
|
const lat = typeof data.latitude === 'string' ? data.latitude : String(data.latitude);
|
|
const lng = typeof data.longitude === 'string' ? data.longitude : String(data.longitude);
|
|
|
|
data['Geo-Location'] = `${lat};${lng}`;
|
|
data.geodata = `${lat};${lng}`;
|
|
|
|
// Keep original values without parsing
|
|
data.latitude = lat;
|
|
data.longitude = lng;
|
|
}
|
|
// If we have Geo-Location but no lat/lng, parse it
|
|
else if (data['Geo-Location'] && (!data.latitude || !data.longitude)) {
|
|
const geoLocation = data['Geo-Location'].toString();
|
|
|
|
// Try semicolon-separated first
|
|
let parts = geoLocation.split(';');
|
|
if (parts.length === 2) {
|
|
// Keep as strings to preserve precision
|
|
const lat = parts[0].trim();
|
|
const lng = parts[1].trim();
|
|
|
|
// Only validate they're numeric, don't convert
|
|
if (!isNaN(parseFloat(lat)) && !isNaN(parseFloat(lng))) {
|
|
data.latitude = lat;
|
|
data.longitude = lng;
|
|
data.geodata = `${lat};${lng}`;
|
|
return data;
|
|
}
|
|
}
|
|
|
|
// Try comma-separated
|
|
parts = geoLocation.split(',');
|
|
if (parts.length === 2) {
|
|
const lat = parts[0].trim();
|
|
const lng = parts[1].trim();
|
|
|
|
if (!isNaN(parseFloat(lat)) && !isNaN(parseFloat(lng))) {
|
|
data.latitude = lat;
|
|
data.longitude = lng;
|
|
data.geodata = `${lat};${lng}`;
|
|
// Normalize Geo-Location to semicolon format for NocoDB GeoData
|
|
data['Geo-Location'] = `${lat};${lng}`;
|
|
}
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// Validate coordinates
|
|
function validateCoordinates(lat, lng) {
|
|
// Keep original string values
|
|
const latStr = typeof lat === 'string' ? lat : String(lat);
|
|
const lngStr = typeof lng === 'string' ? lng : String(lng);
|
|
|
|
// Parse only for validation
|
|
const latitude = parseFloat(latStr);
|
|
const longitude = parseFloat(lngStr);
|
|
|
|
if (isNaN(latitude) || isNaN(longitude)) {
|
|
return { valid: false, error: 'Invalid coordinate values' };
|
|
}
|
|
|
|
if (latitude < -90 || latitude > 90) {
|
|
return { valid: false, error: 'Latitude must be between -90 and 90' };
|
|
}
|
|
|
|
if (longitude < -180 || longitude > 180) {
|
|
return { valid: false, error: 'Longitude must be between -180 and 180' };
|
|
}
|
|
|
|
// Return the original string values to preserve precision
|
|
return { valid: true, latitude: latStr, longitude: lngStr };
|
|
}
|
|
|
|
// Check if coordinates are within bounds
|
|
function checkBounds(lat, lng, bounds) {
|
|
if (!bounds) return true;
|
|
|
|
// Parse only for comparison
|
|
const latitude = parseFloat(lat);
|
|
const longitude = parseFloat(lng);
|
|
|
|
return latitude <= bounds.north &&
|
|
latitude >= bounds.south &&
|
|
longitude <= bounds.east &&
|
|
longitude >= bounds.west;
|
|
}
|
|
|
|
// Validate URL format
|
|
function validateUrl(url) {
|
|
if (!url || typeof url !== 'string') {
|
|
return '';
|
|
}
|
|
|
|
const trimmed = url.trim();
|
|
if (!trimmed) {
|
|
return '';
|
|
}
|
|
|
|
// Basic URL validation
|
|
try {
|
|
new URL(trimmed);
|
|
return trimmed;
|
|
} catch (e) {
|
|
// If not a valid URL, check if it's a relative path or missing protocol
|
|
if (trimmed.startsWith('/') || !trimmed.includes('://')) {
|
|
// For relative paths or missing protocol, return as-is
|
|
return trimmed;
|
|
}
|
|
return '';
|
|
}
|
|
}
|
|
|
|
// Get cookie configuration based on request
|
|
function getCookieConfig(req) {
|
|
const host = req?.get('host') || '';
|
|
const isLocalhost = host.includes('localhost') ||
|
|
host.includes('127.0.0.1') ||
|
|
host.match(/^\d+\.\d+\.\d+\.\d+/);
|
|
|
|
const config = {
|
|
httpOnly: true,
|
|
maxAge: 24 * 60 * 60 * 1000, // 24 hours
|
|
sameSite: 'lax',
|
|
secure: false,
|
|
domain: undefined
|
|
};
|
|
|
|
// Only set domain and secure for production non-localhost access
|
|
if (process.env.NODE_ENV === 'production' && !isLocalhost && process.env.COOKIE_DOMAIN) {
|
|
const cookieDomain = process.env.COOKIE_DOMAIN.replace(/^\./, '');
|
|
if (host.includes(cookieDomain)) {
|
|
config.domain = process.env.COOKIE_DOMAIN;
|
|
config.secure = true;
|
|
}
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
// Extract ID from NocoDB response
|
|
function extractId(record) {
|
|
return record.Id || record.id || record.ID || record._id;
|
|
}
|
|
|
|
// Sanitize user data for response
|
|
function sanitizeUser(user) {
|
|
const { Password, password, ...safeUser } = user;
|
|
return safeUser;
|
|
}
|
|
|
|
// Extract walk sheet configuration from NocoDB data, handling different field name formats
|
|
function extractWalkSheetConfig(data, defaults = {}) {
|
|
if (!data) return defaults;
|
|
|
|
return {
|
|
walk_sheet_title: data.walk_sheet_title !== undefined ? data.walk_sheet_title :
|
|
data['Walk Sheet Title'] !== undefined ? data['Walk Sheet Title'] :
|
|
defaults.walk_sheet_title,
|
|
walk_sheet_subtitle: data.walk_sheet_subtitle !== undefined ? data.walk_sheet_subtitle :
|
|
data['Walk Sheet Subtitle'] !== undefined ? data['Walk Sheet Subtitle'] :
|
|
defaults.walk_sheet_subtitle,
|
|
walk_sheet_footer: data.walk_sheet_footer !== undefined ? data.walk_sheet_footer :
|
|
data['Walk Sheet Footer'] !== undefined ? data['Walk Sheet Footer'] :
|
|
defaults.walk_sheet_footer,
|
|
qr_code_1_url: data.qr_code_1_url !== undefined ? data.qr_code_1_url :
|
|
data['QR Code 1 URL'] !== undefined ? data['QR Code 1 URL'] :
|
|
defaults.qr_code_1_url,
|
|
qr_code_1_label: data.qr_code_1_label !== undefined ? data.qr_code_1_label :
|
|
data['QR Code 1 Label'] !== undefined ? data['QR Code 1 Label'] :
|
|
defaults.qr_code_1_label,
|
|
qr_code_2_url: data.qr_code_2_url !== undefined ? data.qr_code_2_url :
|
|
data['QR Code 2 URL'] !== undefined ? data['QR Code 2 URL'] :
|
|
defaults.qr_code_2_url,
|
|
qr_code_2_label: data.qr_code_2_label !== undefined ? data.qr_code_2_label :
|
|
data['QR Code 2 Label'] !== undefined ? data['QR Code 2 Label'] :
|
|
defaults.qr_code_2_label,
|
|
qr_code_3_url: data.qr_code_3_url !== undefined ? data.qr_code_3_url :
|
|
data['QR Code 3 URL'] !== undefined ? data['QR Code 3 URL'] :
|
|
defaults.qr_code_3_url,
|
|
qr_code_3_label: data.qr_code_3_label !== undefined ? data.qr_code_3_label :
|
|
data['QR Code 3 Label'] !== undefined ? data['QR Code 3 Label'] :
|
|
defaults.qr_code_3_label
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
syncGeoFields,
|
|
validateUrl,
|
|
getCookieConfig,
|
|
extractId,
|
|
validateCoordinates,
|
|
checkBounds,
|
|
sanitizeUser,
|
|
extractWalkSheetConfig
|
|
}; |