const pool = require('../config/database'); const redisClient = require('../config/redis'); const logger = require('../config/logger'); const CACHE_TTL = 3600; class StopsController { // Get all stops async getAllStops(req, res) { try { const cached = await redisClient.get('stops:all'); if (cached) { return res.json(JSON.parse(cached)); } const result = await pool.query(` SELECT s.*, ST_Y(s.location::geometry) as latitude, ST_X(s.location::geometry) as longitude, COUNT(DISTINCT rs.route_id) as routes_count FROM stops s LEFT JOIN route_stops rs ON s.id = rs.stop_id WHERE s.is_active = true GROUP BY s.id ORDER BY s.name `); const stops = result.rows; await redisClient.setEx('stops:all', CACHE_TTL, JSON.stringify(stops)); res.json(stops); } catch (error) { logger.error('Error fetching stops:', error); res.status(500).json({ error: 'Failed to fetch stops' }); } } // Get stop by ID async getStopById(req, res) { const { id } = req.params; try { const result = await pool.query(` SELECT s.*, ST_Y(s.location::geometry) as latitude, ST_X(s.location::geometry) as longitude FROM stops s WHERE s.id = $1 AND s.is_active = true `, [id]); if (result.rows.length === 0) { return res.status(404).json({ error: 'Stop not found' }); } // Get routes passing through this stop const routesResult = await pool.query(` SELECT r.*, rs.sequence, rs.time_offset_minutes FROM routes r JOIN route_stops rs ON r.id = rs.route_id WHERE rs.stop_id = $1 AND r.is_active = true ORDER BY r.route_number `, [id]); const stop = { ...result.rows[0], routes: routesResult.rows }; res.json(stop); } catch (error) { logger.error('Error fetching stop:', error); res.status(500).json({ error: 'Failed to fetch stop' }); } } // Get nearby stops (PostGIS query) async getNearbyStops(req, res) { const { lat, lon, radius = 500 } = req.query; if (!lat || !lon) { return res.status(400).json({ error: 'Missing required parameters', required: ['lat', 'lon'] }); } try { const result = await pool.query(` SELECT s.*, ST_Y(s.location::geometry) as latitude, ST_X(s.location::geometry) as longitude, ST_Distance( s.location, ST_MakePoint($2, $1)::geography ) as distance_meters FROM stops s WHERE s.is_active = true AND ST_DWithin( s.location, ST_MakePoint($2, $1)::geography, $3 ) ORDER BY distance_meters LIMIT 20 `, [parseFloat(lat), parseFloat(lon), parseFloat(radius)]); res.json(result.rows); } catch (error) { logger.error('Error fetching nearby stops:', error); res.status(500).json({ error: 'Failed to fetch nearby stops' }); } } // Create stop async createStop(req, res) { const { name, latitude, longitude, address, description } = req.body; if (!name || !latitude || !longitude) { return res.status(400).json({ error: 'Missing required fields', required: ['name', 'latitude', 'longitude'] }); } try { const result = await pool.query(` INSERT INTO stops (name, location, address, description) VALUES ( $1, ST_SetSRID(ST_MakePoint($3, $2), 4326)::geography, $4, $5 ) RETURNING *, ST_Y(location::geometry) as latitude, ST_X(location::geometry) as longitude `, [name, latitude, longitude, address, description]); await redisClient.del('stops:all'); logger.info(`Stop created: ${name}`); res.status(201).json(result.rows[0]); } catch (error) { logger.error('Error creating stop:', error); res.status(500).json({ error: 'Failed to create stop' }); } } // Update stop async updateStop(req, res) { const { id } = req.params; const { name, latitude, longitude, address, description, is_active } = req.body; try { let query = 'UPDATE stops SET '; const values = []; let paramCount = 1; if (name !== undefined) { query += `name = $${paramCount}, `; values.push(name); paramCount++; } if (latitude !== undefined && longitude !== undefined) { query += `location = ST_SetSRID(ST_MakePoint($${paramCount + 1}, $${paramCount}), 4326)::geography, `; values.push(latitude, longitude); paramCount += 2; } if (address !== undefined) { query += `address = $${paramCount}, `; values.push(address); paramCount++; } if (description !== undefined) { query += `description = $${paramCount}, `; values.push(description); paramCount++; } if (is_active !== undefined) { query += `is_active = $${paramCount}, `; values.push(is_active); paramCount++; } query = query.slice(0, -2); // Remove trailing comma query += ` WHERE id = $${paramCount} RETURNING *, ST_Y(location::geometry) as latitude, ST_X(location::geometry) as longitude`; values.push(id); const result = await pool.query(query, values); if (result.rows.length === 0) { return res.status(404).json({ error: 'Stop not found' }); } await redisClient.del('stops:all'); logger.info(`Stop updated: ${id}`); res.json(result.rows[0]); } catch (error) { logger.error('Error updating stop:', error); res.status(500).json({ error: 'Failed to update stop' }); } } // Delete stop async deleteStop(req, res) { const { id } = req.params; try { const result = await pool.query( 'DELETE FROM stops WHERE id = $1 RETURNING *', [id] ); if (result.rows.length === 0) { return res.status(404).json({ error: 'Stop not found' }); } await redisClient.del('stops:all'); logger.info(`Stop deleted: ${id}`); res.json({ message: 'Stop deleted successfully' }); } catch (error) { logger.error('Error deleting stop:', error); res.status(500).json({ error: 'Failed to delete stop' }); } } } module.exports = new StopsController();