Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ dhall-configs/secrets/gtfs_in_memory_server_rust.dhall
# Temporary files
*.tmp
*.temp
CLAUDE.md
.claude/

# Unused files
ignored/
12 changes: 11 additions & 1 deletion dhall-configs/dev/gtfs_in_memory_server_rust.dhall
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ let logger_cfg = {
log_to_file = False
}

let secrets = ../secrets/gtfs_in_memory_server_rust.example.dhall

in {
-- Logger configuration
logger_cfg = logger_cfg,
Expand Down Expand Up @@ -55,5 +57,13 @@ in {

-- Bhubaneswar vehicle cache configuration
bhubaneswar_cache_update_interval = 10,
phone_number_hash_key = "HASH_KEY"
phone_number_hash_key = "HASH_KEY",

enable_schedule_reconciliation=True,
-- OSRTC station cache configuration
osrtc_base_url = Some "OSRTC_BASE_URL",
osrtc_username = secrets.osrtc_username,
osrtc_secret_key = secrets.osrtc_secret_key,
osrtc_station_refresh_interval_hours = 1,
osrtc_feed_key = Some "odisha_osrtc"
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
otp_url = "OTP_URL",
bhubaneswar_external_auth = Some "TEXT",
phone_number_hash_key = Some "TEXT",
enable_schedule_reconciliation = False
enable_schedule_reconciliation = False,
osrtc_username = Some "OSRTC_USERNAME",
osrtc_secret_key = Some "OSRTC_PASSWORD"
}
36 changes: 32 additions & 4 deletions src/environment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use crate::services::{
fleet_operator::{DBFleetOperatorService, FleetOperatorService, MockFleetOperatorService},
gtfs_service::GTFSService,
operator::{DBOperatorService, MockOperatorService, OperatorService},
osrtc_station_cache::OsrtcStationCache,
trip_service::TripService,
};
use crate::tools::dhall::read_dhall_config as dhall_read_config;
Expand Down Expand Up @@ -72,6 +73,11 @@ pub struct AppConfig {
pub phone_number_hash_key: String,
/// Enable schedule-based active trip reconciliation (default: false)
pub enable_schedule_reconciliation: bool,
pub osrtc_base_url: Option<String>,
pub osrtc_username: Option<String>,
pub osrtc_secret_key: Option<String>,
pub osrtc_station_refresh_interval_hours: u64,
pub osrtc_feed_key: Option<String>,
}

impl OtpConfig {
Expand Down Expand Up @@ -137,6 +143,7 @@ pub struct AppState {
pub fleet_operator_service: Arc<dyn FleetOperatorService>,
pub trip_service: Arc<TripService>,
pub chalo_vehicle_cache: Arc<ChaloVehicleCache>,
pub osrtc_cache: Option<Arc<OsrtcStationCache>>,
pub config: AppConfig,
pub bus_registration_mapping: Arc<HashMap<String, HashMap<String, String>>>,
pub fleet_list: Arc<HashMap<String, HashMap<String, Vec<String>>>>,
Expand Down Expand Up @@ -299,6 +306,29 @@ impl AppState {
chalo_vehicle_cache.set_update_interval(app_config.bhubaneswar_cache_update_interval);
let chalo_vehicle_cache = Arc::new(chalo_vehicle_cache);

let osrtc_cache = match (
&app_config.osrtc_base_url,
&app_config.osrtc_username,
&app_config.osrtc_secret_key,
) {
(Some(base_url), Some(username), Some(secret_key)) => {
let cache = OsrtcStationCache::new(
base_url.clone(),
username.clone(),
secret_key.clone(),
app_config.osrtc_station_refresh_interval_hours * 3600,
)?;
if let Err(e) = cache.initialize().await {
error!("OSRTC station cache initial load failed (will retry in background): {}", e);
}
Some(Arc::new(cache))
}
_ => {
info!("OSRTC credentials not configured; OSRTC station cache disabled");
None
}
};

// Load bus registration mapping from CSV
let bus_registration_mapping = Arc::new(Self::load_bus_registration_mapping().await?);

Expand All @@ -317,6 +347,7 @@ impl AppState {
fleet_operator_service,
trip_service,
chalo_vehicle_cache,
osrtc_cache,
config: app_config,
bus_registration_mapping,
fleet_list: Arc::new(Self::load_fleet_list().await?),
Expand Down Expand Up @@ -379,10 +410,7 @@ impl AppState {
}
}

info!(
"Loaded fleet tag list for {} GTFS feeds",
fleet_map.len()
);
info!("Loaded fleet tag list for {} GTFS feeds", fleet_map.len());

Ok(fleet_map)
}
Expand Down
28 changes: 27 additions & 1 deletion src/handlers/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use crate::models::{
StopCodeFromProviderStopCodeResponse, VehicleMetadataResponse, VehicleServiceTypeResponse,
};
use crate::services::db_vehicle_reader::{chalo_gtfs_ids, is_chalo_gtfs_id};
use crate::services::osrtc_station_cache::osrtc_station_to_route_stop_mapping;
// alias for query param map (string->string)
type MapStringString = std::collections::HashMap<String, String>;
use crate::{
Expand Down Expand Up @@ -578,6 +579,18 @@ async fn get_routes_fuzzy(

async fn get_stops(app_state: Data<AppState>, path: Path<String>) -> AppResult<HttpResponse> {
let gtfs_id = path.into_inner();
if app_state.config.osrtc_feed_key.as_deref() == Some(gtfs_id.as_str()) {
let cache = app_state
.osrtc_cache
.as_ref()
.ok_or_else(|| AppError::Internal("OSRTC cache not configured".to_string()))?;
let stations = cache.get_all_stations().await;
let mappings: Vec<RouteStopMapping> = stations
.iter()
.map(osrtc_station_to_route_stop_mapping)
.collect();
return Ok(HttpResponse::Ok().json(mappings));
}
let stops = app_state.gtfs_service.get_stops(&gtfs_id).await?;
Ok(HttpResponse::Ok().json(stops))
}
Expand Down Expand Up @@ -620,6 +633,17 @@ async fn get_stop(
path: Path<(String, String)>,
) -> AppResult<HttpResponse> {
let (gtfs_id, stop_code) = path.into_inner();
if app_state.config.osrtc_feed_key.as_deref() == Some(gtfs_id.as_str()) {
let cache = app_state
.osrtc_cache
.as_ref()
.ok_or_else(|| AppError::Internal("OSRTC cache not configured".to_string()))?;
let station = cache
.get_station_by_id(&stop_code)
.await
.ok_or_else(|| AppError::NotFound(format!("OSRTC station not found: {stop_code}")))?;
return Ok(HttpResponse::Ok().json(osrtc_station_to_route_stop_mapping(&station)));
}
let (stop, maybe_mapping) = app_state
.gtfs_service
.get_stop(&gtfs_id, &stop_code)
Expand Down Expand Up @@ -2901,7 +2925,9 @@ async fn fleet_operator_trip_action(
let trip_number = match action {
TripAction::Reset => 0,
_ => req.trip_number.ok_or_else(|| {
AppError::BadRequest("trip_number is required for 'start' and 'end' actions.".to_string())
AppError::BadRequest(
"trip_number is required for 'start' and 'end' actions.".to_string(),
)
})?,
};

Expand Down
7 changes: 7 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ async fn main() -> anyhow::Result<()> {
chalo_cache_clone.start_background_update_task().await;
});

// Start background task to periodically refresh OSRTC station list
if let Some(osrtc_cache) = app_state.osrtc_cache.clone() {
tokio::spawn(async move {
osrtc_cache.start_background_refresh_task().await;
});
}

let prometheus = prometheus_metrics();

// Create and run the web server with performance optimizations
Expand Down
7 changes: 3 additions & 4 deletions src/services/chalo_vehicle_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use crate::services::gtfs_service::GTFSService;
use crate::tools::error::{AppError, AppResult};
use chrono::{DateTime, Utc};
use csv::ReaderBuilder;
use reqwest::Client;
use once_cell::sync::Lazy;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
Expand Down Expand Up @@ -95,9 +95,8 @@ pub const CHALO_CITY_DEFS: [ChaloCityDef; 2] = [

/// Membership for [`CHALO_CITY_DEFS`], built once per process. The set only changes with a deploy
/// (same as edits to [`CHALO_CITY_DEFS`]), so no runtime invalidation.
static CHALO_GTFS_ID_SET: Lazy<HashSet<&'static str>> = Lazy::new(|| {
CHALO_CITY_DEFS.iter().map(|c| c.gtfs_id).collect()
});
static CHALO_GTFS_ID_SET: Lazy<HashSet<&'static str>> =
Lazy::new(|| CHALO_CITY_DEFS.iter().map(|c| c.gtfs_id).collect());

/// Same order as [`CHALO_CITY_DEFS`]; built once per process (see [`CHALO_GTFS_ID_SET`]).
static CHALO_GTFS_IDS_SLICE: Lazy<Box<[&'static str]>> = Lazy::new(|| {
Expand Down
3 changes: 2 additions & 1 deletion src/services/fleet_operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -843,7 +843,8 @@ impl FleetOperatorService for DBFleetOperatorService {
if action != TripAction::Reset {
self.validate_trip_exists(&waybill, trip_number).await?;
}
self.apply_trip_action(&waybill, &action, trip_number, timestamp).await?;
self.apply_trip_action(&waybill, &action, trip_number, timestamp)
.await?;
Ok(TripActionResponse { success: true })
}

Expand Down
1 change: 1 addition & 0 deletions src/services/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ pub mod field_generator;
pub mod fleet_operator;
pub mod gtfs_service;
pub mod operator;
pub mod osrtc_station_cache;
pub mod trip_service;
Loading
Loading