- Split server.js routes into modular files (server/routes/) - departures.js: Departure data endpoints - sites.js: Site search and nearby sites - config.js: Configuration endpoints - Reorganized file structure following Node.js best practices: - Moved sites-config.json to config/sites.json - Moved API_RESPONSE_DOCUMENTATION.md to docs/ - Moved raspberry-pi-setup.sh to scripts/ - Archived legacy files to archive/ directory - Updated all code references to new file locations - Added archive/ to .gitignore to exclude legacy files from repo - Updated README.md with new structure and organization - All functionality tested and working correctly Version: 1.2.0
153 lines
4.9 KiB
JavaScript
153 lines
4.9 KiB
JavaScript
/**
|
|
* Departures route handler
|
|
* Handles fetching and returning transit departure data
|
|
*/
|
|
|
|
const https = require('https');
|
|
|
|
/**
|
|
* Fetch departures for a specific site from SL Transport API
|
|
* @param {string} siteId - The site ID to fetch departures for
|
|
* @returns {Promise<Object>} - Departure data
|
|
*/
|
|
function fetchDeparturesForSite(siteId) {
|
|
return new Promise((resolve, reject) => {
|
|
const apiUrl = `https://transport.integration.sl.se/v1/sites/${siteId}/departures`;
|
|
console.log(`Fetching data from: ${apiUrl}`);
|
|
|
|
https.get(apiUrl, (res) => {
|
|
let data = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk;
|
|
});
|
|
|
|
res.on('end', () => {
|
|
console.log('Raw API response:', data.substring(0, 200) + '...');
|
|
|
|
try {
|
|
try {
|
|
const parsedData = JSON.parse(data);
|
|
console.log('Successfully parsed as regular JSON');
|
|
resolve(parsedData);
|
|
return;
|
|
} catch (jsonError) {
|
|
console.log('Not valid JSON, trying to fix format...');
|
|
}
|
|
|
|
if (data.startsWith('departures":')) {
|
|
data = '{' + data;
|
|
} else if (data.includes('departures":')) {
|
|
const startIndex = data.indexOf('departures":');
|
|
if (startIndex > 0) {
|
|
data = '{' + data.substring(startIndex);
|
|
}
|
|
}
|
|
|
|
data = data.replace(/}{\s*"/g, '},{"');
|
|
data = data.replace(/"([^"]+)":\s*([^,{}\[\]]+)(?=")/g, '"$1": $2,');
|
|
data = data.replace(/,\s*}/g, '}').replace(/,\s*\]/g, ']');
|
|
|
|
try {
|
|
const parsedData = JSON.parse(data);
|
|
console.log('Successfully parsed fixed JSON');
|
|
|
|
if (parsedData && parsedData.departures && parsedData.departures.length > 0) {
|
|
console.log('Sample departure structure:', JSON.stringify(parsedData.departures[0], null, 2));
|
|
|
|
const sample = parsedData.departures[0];
|
|
console.log('Direction fields:', {
|
|
direction: sample.direction,
|
|
directionText: sample.directionText,
|
|
directionCode: sample.directionCode,
|
|
destination: sample.destination
|
|
});
|
|
}
|
|
|
|
resolve(parsedData);
|
|
} catch (parseError) {
|
|
console.error('Failed to parse even after fixing:', parseError);
|
|
// Return empty departures array instead of rejecting to be more resilient
|
|
resolve({
|
|
departures: [],
|
|
error: 'Failed to parse API response: ' + parseError.message
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error processing API response:', error);
|
|
// Return empty departures array instead of rejecting to be more resilient
|
|
resolve({
|
|
departures: [],
|
|
error: 'Error processing API response: ' + error.message
|
|
});
|
|
}
|
|
});
|
|
}).on('error', (error) => {
|
|
console.error('Error fetching departures:', error);
|
|
reject(error);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Fetch departures for all enabled sites
|
|
* @param {Array} enabledSites - Array of enabled site configurations
|
|
* @returns {Promise<Object>} - Object with sites array containing departure data
|
|
*/
|
|
async function fetchAllDepartures(enabledSites) {
|
|
if (enabledSites.length === 0) {
|
|
return { sites: [], error: 'No enabled sites configured' };
|
|
}
|
|
|
|
try {
|
|
const sitesPromises = enabledSites.map(async (site) => {
|
|
try {
|
|
const departureData = await fetchDeparturesForSite(site.id);
|
|
return {
|
|
siteId: site.id,
|
|
siteName: site.name,
|
|
data: departureData
|
|
};
|
|
} catch (error) {
|
|
console.error(`Error fetching departures for site ${site.id}:`, error);
|
|
return {
|
|
siteId: site.id,
|
|
siteName: site.name,
|
|
error: error.message
|
|
};
|
|
}
|
|
});
|
|
|
|
const results = await Promise.all(sitesPromises);
|
|
return { sites: results };
|
|
} catch (error) {
|
|
console.error('Error fetching all departures:', error);
|
|
return { sites: [], error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle departures API endpoint
|
|
* @param {http.IncomingMessage} req - HTTP request object
|
|
* @param {http.ServerResponse} res - HTTP response object
|
|
* @param {Object} config - Application configuration
|
|
*/
|
|
async function handleDepartures(req, res, config) {
|
|
try {
|
|
const enabledSites = config.sites.filter(site => site.enabled);
|
|
const data = await fetchAllDepartures(enabledSites);
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(data));
|
|
} catch (error) {
|
|
console.error('Error handling departures request:', error);
|
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: error.message }));
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
handleDepartures,
|
|
fetchDeparturesForSite,
|
|
fetchAllDepartures
|
|
};
|