const axios = require('axios'); const logger = require('../utils/logger'); const SOCRATA_DOMAIN = 'https://data.edmonton.ca'; class SocrataService { constructor() { this.client = axios.create({ baseURL: SOCRATA_DOMAIN, timeout: 30000 // 30 seconds for potentially large datasets }); this.client.interceptors.response.use( response => response, error => { logger.error('Socrata API Error:', { message: error.message, url: error.config?.url, status: error.response?.status, data: error.response?.data }); throw error; } ); } /** * Fetches data from a Socrata dataset. * @param {string} datasetId - The ID of the dataset (e.g., 'nggt-rwac'). * @param {object} params - SoQL query parameters. * @returns {Promise} A promise that resolves to an array of records. */ async get(datasetId, params = {}) { try { logger.info(`Fetching Socrata dataset ${datasetId} with params:`, params); // Socrata uses an app token for higher rate limits, but it's not required for public data. // We can add an X-App-Token header here if one is obtained. const response = await this.client.get(`/resource/${datasetId}.json`, { params }); logger.info(`Successfully fetched ${response.data.length} records from Socrata dataset ${datasetId}`); return response.data; } catch (error) { logger.error(`Failed to fetch Socrata dataset ${datasetId}`, { message: error.message, status: error.response?.status, statusText: error.response?.statusText, data: error.response?.data, url: error.config?.url, params: params }); // Provide more specific error messages if (error.response?.status === 404) { throw new Error(`Dataset ${datasetId} not found on Socrata API`); } else if (error.response?.status === 400) { throw new Error(`Invalid query parameters for dataset ${datasetId}`); } else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') { throw new Error('Unable to connect to Edmonton Open Data Portal'); } throw new Error('Could not retrieve data from the external source.'); } } } module.exports = new SocrataService();