const axios = require('axios'); class RepresentAPIService { constructor() { this.baseURL = process.env.REPRESENT_API_BASE || 'https://represent.opennorth.ca'; this.rateLimit = parseInt(process.env.REPRESENT_API_RATE_LIMIT) || 60; this.lastRequestTime = 0; this.requestCount = 0; this.resetTime = Date.now() + 60000; // Reset every minute } async checkRateLimit() { const now = Date.now(); // Reset counter if a minute has passed if (now > this.resetTime) { this.requestCount = 0; this.resetTime = now + 60000; } // Check if we're at the rate limit if (this.requestCount >= this.rateLimit) { const waitTime = this.resetTime - now; throw new Error(`Rate limit exceeded. Please wait ${Math.ceil(waitTime / 1000)} seconds.`); } this.requestCount++; this.lastRequestTime = now; } async makeRequest(endpoint) { await this.checkRateLimit(); try { const response = await axios.get(`${this.baseURL}${endpoint}`, { timeout: 10000, headers: { 'User-Agent': 'Alberta-Influence-Campaign-Tool/1.0' } }); return response.data; } catch (error) { if (error.response) { throw new Error(`API Error: ${error.response.status} - ${error.response.statusText}`); } else if (error.request) { throw new Error('Network error: Unable to reach Represent API'); } else { throw new Error(`Request error: ${error.message}`); } } } async testConnection() { try { const data = await this.makeRequest('/boundary-sets/?limit=1'); return { success: true, message: 'Successfully connected to Represent API', sampleData: data }; } catch (error) { return { success: false, message: 'Failed to connect to Represent API', error: error.message }; } } async getRepresentativesByPostalCode(postalCode) { const formattedPostalCode = postalCode.replace(/\s/g, '').toUpperCase(); // Validate Alberta postal code (should start with T) if (!formattedPostalCode.startsWith('T')) { throw new Error('This tool is designed for Alberta postal codes only (starting with T)'); } try { const endpoint = `/postcodes/${formattedPostalCode}/`; console.log(`Making Represent API request to: ${this.baseURL}${endpoint}`); const data = await this.makeRequest(endpoint); console.log('Represent API Response:', JSON.stringify(data, null, 2)); console.log(`Representatives concordance count: ${data.representatives_concordance?.length || 0}`); console.log(`Representatives centroid count: ${data.representatives_centroid?.length || 0}`); return { postalCode: formattedPostalCode, city: data.city, province: data.province, centroid: data.centroid, representatives_concordance: data.representatives_concordance || [], representatives_centroid: data.representatives_centroid || [], boundaries_concordance: data.boundaries_concordance || [], boundaries_centroid: data.boundaries_centroid || [] }; } catch (error) { console.error(`Represent API error for ${formattedPostalCode}:`, error.message); throw new Error(`Failed to fetch data for postal code ${formattedPostalCode}: ${error.message}`); } } async getRepresentativeDetails(representativeUrl) { try { // Extract the path from the URL const urlPath = representativeUrl.replace(this.baseURL, ''); const data = await this.makeRequest(urlPath); return data; } catch (error) { throw new Error(`Failed to fetch representative details: ${error.message}`); } } async getBoundaryDetails(boundaryUrl) { try { // Extract the path from the URL const urlPath = boundaryUrl.replace(this.baseURL, ''); const data = await this.makeRequest(urlPath); return data; } catch (error) { throw new Error(`Failed to fetch boundary details: ${error.message}`); } } async searchRepresentatives(filters = {}) { try { const queryParams = new URLSearchParams(); // Add filters as query parameters Object.keys(filters).forEach(key => { if (filters[key]) { queryParams.append(key, filters[key]); } }); const endpoint = `/representatives/?${queryParams.toString()}`; const data = await this.makeRequest(endpoint); return data; } catch (error) { throw new Error(`Failed to search representatives: ${error.message}`); } } } module.exports = new RepresentAPIService();