You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
6.5 KiB
JavaScript

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();