feat: implement multi-city support with city management features
- Added a new cities table to the database to manage multiple cities independently. - Created a middleware to resolve city context based on X-City-Id header. - Updated routes and services to handle city-specific data for routes, stops, and alerts. - Implemented a CityContext in the frontend to manage active city state and provide city data across the application. - Added a CitySelector component for users to switch between cities. - Created a CitiesPage for admins to manage cities, including adding new cities with validation. - Updated various components and pages to reflect the active city context, ensuring data is displayed correctly based on the selected city. - Enhanced the schedule import functionality to restrict imports based on the active city.
This commit is contained in:
46
backend/src/middleware/city.js
Normal file
46
backend/src/middleware/city.js
Normal file
@@ -0,0 +1,46 @@
|
||||
const pool = require('../config/database');
|
||||
const logger = require('../config/logger');
|
||||
|
||||
const DEFAULT_CITY_SLUG = 'polotsk';
|
||||
|
||||
async function resolveCity(req, res, next) {
|
||||
const rawCityId = req.get('X-City-Id');
|
||||
|
||||
if (rawCityId !== undefined && (!/^\d+$/.test(rawCityId) || Number(rawCityId) <= 0)) {
|
||||
return res.status(400).json({ error: 'Invalid city identifier' });
|
||||
}
|
||||
|
||||
try {
|
||||
const result = rawCityId
|
||||
? await pool.query('SELECT * FROM cities WHERE id = $1 AND is_active = true', [Number(rawCityId)])
|
||||
: await pool.query('SELECT * FROM cities WHERE slug = $1 AND is_active = true', [DEFAULT_CITY_SLUG]);
|
||||
|
||||
if (!result.rows.length) {
|
||||
return res.status(404).json({ error: 'City not found or inactive' });
|
||||
}
|
||||
|
||||
req.city = result.rows[0];
|
||||
return next();
|
||||
} catch (error) {
|
||||
logger.error('Unable to resolve city context', { message: error.message });
|
||||
return res.status(500).json({ error: 'Unable to resolve city' });
|
||||
}
|
||||
}
|
||||
|
||||
async function getRouteInCity(routeId, cityId, db = pool) {
|
||||
const result = await db.query(
|
||||
'SELECT * FROM routes WHERE id = $1 AND city_id = $2',
|
||||
[routeId, cityId]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
async function getStopInCity(stopId, cityId, db = pool) {
|
||||
const result = await db.query(
|
||||
'SELECT * FROM stops WHERE id = $1 AND city_id = $2',
|
||||
[stopId, cityId]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
}
|
||||
|
||||
module.exports = { DEFAULT_CITY_SLUG, resolveCity, getRouteInCity, getStopInCity };
|
||||
Reference in New Issue
Block a user