diff --git a/api/src/controllers/route.rs b/api/src/controllers/route.rs index 6d5f391..782e36c 100644 --- a/api/src/controllers/route.rs +++ b/api/src/controllers/route.rs @@ -1,5 +1,5 @@ use actix_web::{get, web::{Data, self}, HttpResponse, Responder}; -use std::{cmp::Ordering, sync::Arc}; +use std::{cmp::Ordering, time::Instant, sync::Arc}; use libseptastic::{route::RouteType, stop_schedule::Trip}; use serde::{Serialize, Deserialize}; use askama::Template; @@ -9,6 +9,8 @@ use crate::database; #[get("/routes")] async fn get_routes_html(state: Data>) -> impl Responder { + let start_time = Instant::now(); + let mut all_routes = database::get_all_routes(&mut state.database.begin().await.unwrap()).await.unwrap(); all_routes.sort_by(|x, y| { @@ -21,15 +23,20 @@ async fn get_routes_html(state: Data>) -> impl Responder { return if y.id > x.id { Ordering::Less } else { Ordering::Greater }; }); + let rr_routes = all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::RegionalRail).collect(); + let subway_routes = all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::SubwayElevated).collect(); + let trolley_routes = all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::Trolley).collect(); + let bus_routes = all_routes.into_iter().filter(|x| x.route_type == RouteType::TracklessTrolley || x.route_type == RouteType::Bus).collect(); HttpResponse::Ok().body(crate::templates::ContentTemplate { - page_title: None, - page_desc: None, + page_title: Some(String::from("SEPTASTIC | Routes")), + page_desc: Some(String::from("All SEPTA routes.")), content: crate::templates::RoutesTemplate { - rr_routes: all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::RegionalRail).collect(), - subway_routes: all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::SubwayElevated).collect(), - trolley_routes: all_routes.clone().into_iter().filter(|x| x.route_type == RouteType::Trolley).collect(), - bus_routes: all_routes.into_iter().filter(|x| x.route_type == RouteType::TracklessTrolley || x.route_type == RouteType::Bus).collect(), - } + rr_routes, + subway_routes, + trolley_routes, + bus_routes, + }, + load_time_ms: Some(start_time.elapsed().as_millis()) }.render().unwrap()) } @@ -58,6 +65,7 @@ pub struct RouteResponse { } async fn get_route_info(route_id: String, state: Data>) -> ::anyhow::Result { + let route = get_route_by_id(route_id.clone(), state.clone()).await?; let directions = database::get_direction_by_route_id(route_id.clone(), &mut state.database.begin().await?).await?; let mut trips = database::get_schedule_by_route_id(route_id.clone(), &mut state.database.begin().await?).await?; @@ -73,6 +81,8 @@ async fn get_route_info(route_id: String, state: Data>) -> ::anyho #[get("/route/{route_id}")] async fn get_route(state: Data>, info: web::Query, path: web::Path) -> impl Responder { + + let start_time = Instant::now(); let mut filters: Option> = None; if let Some (stops_v) = info.stops.clone() { let mut items = Vec::new(); @@ -84,17 +94,17 @@ async fn get_route(state: Data>, info: web::Query, } let route_id = path.into_inner(); - let route_info_r = get_route_info(route_id, state).await; + let route_info_r = get_route_info(route_id.clone(), state).await; if let Ok(route_info) = route_info_r { HttpResponse::Ok().body(crate::templates::ContentTemplate { - page_title: None, - page_desc: None, + page_title: Some(format!("SEPTASTIC | Schedules for {}", route_id.clone())), + page_desc: Some(format!("Schedule information for {}", route_id.clone())), content: crate::templates::RouteTemplate { route: route_info.route, - directions: route_info.directions.clone(), timetables: crate::templates::build_timetables(route_info.directions.as_slice(), route_info.schedule.as_slice()), filter_stops: filters.clone() - } + }, + load_time_ms: Some(start_time.elapsed().as_millis()) }.render().unwrap()) } else { HttpResponse::InternalServerError().body("Error") diff --git a/api/src/database.rs b/api/src/database.rs index c790ddc..a7c0c0d 100644 --- a/api/src/database.rs +++ b/api/src/database.rs @@ -1,6 +1,4 @@ use std::collections::HashMap; -use libseptastic::{direction::CardinalDirection, route::RouteType}; -use serde::{Deserialize, Serialize}; use sqlx::{Postgres, Transaction}; use libseptastic::{stop_schedule::{Trip, TripTracking, StopSchedule}}; @@ -177,142 +175,3 @@ pub async fn get_schedule_by_route_id( return Ok(res); } - -#[derive(Serialize,Deserialize,Clone)] -pub struct NTALive { - delay: i64, - cancelled: bool, - next_stop: Option -} - -#[derive(Serialize,Deserialize)] -pub struct LiveData { - route_id: String, - service_id: String, - trip_id: String, - trip_headsign: String, - direction_id: i64, - block_id: String, - start_time: String, - end_time: String, - delay: i64, - status: String, - lat: Option, - lon: Option, - heading: Option, - next_stop_id: Option, - next_stop_name: Option, - next_stop_sequence: Option, - seat_availability: String, - vehicle_id: String, - timestamp: i64 -} - -#[derive(Serialize,Deserialize)] -pub struct NTAEntry { - route_id: String, - route_type: RouteType, - route_name: String, - color_hex: String, - trip_id: String, - arrival_time: i64, - direction: CardinalDirection, - direction_destination: String, - live: Option -} - -#[derive(Serialize,Deserialize)] -pub struct NTAResult { - station_name: String, - arrivals: Vec -} - -pub async fn get_nta_by_stop_id( - ids: Vec, - start_time: chrono::DateTime, - end_time: chrono::DateTime, - transaction: &mut Transaction<'_, Postgres>, -) -> ::anyhow::Result { - let local_start = start_time.with_timezone(&chrono_tz::America::New_York); - let local_end = end_time.with_timezone(&chrono_tz::America::New_York); - let local_midnight = chrono::Utc::now().with_timezone(&chrono_tz::America::New_York).date().and_hms(0,0,0); - let start_secs = local_start.signed_duration_since(local_midnight).num_seconds(); - let end_secs = local_end.signed_duration_since(local_midnight).num_seconds(); - - let schedule_day = chrono::Utc::now().with_timezone(&chrono_tz::America::New_York); - let schedule_day_str = schedule_day.format("%Y%m%d").to_string(); - - let name_row = sqlx::query!("SELECT name FROM septa_stops WHERE id = $1", ids[0]).fetch_one(&mut **transaction).await?; - - let stop_name = name_row.name; - - let rows: Vec<(String, RouteType, String, String, i64, CardinalDirection, String, String,)> = sqlx::query_as( - r#"SELECT - septa_stop_schedules.route_id, - route_type as "route_type: libseptastic::route::RouteType", - septa_routes.color_hex, - trip_id, - arrival_time, - septa_directions.direction as "direction: libseptastic::direction::CardinalDirection", - septa_directions.direction_destination, - septa_routes.name - FROM - septa_stop_schedules - INNER JOIN septa_directions - ON - septa_directions.direction_id = septa_stop_schedules.direction_id - AND - septa_directions.route_id = septa_stop_schedules.route_id - INNER JOIN septa_stops - ON septa_stops.id = septa_stop_schedules.stop_id - INNER JOIN septa_routes - ON septa_routes.id = septa_stop_schedules.route_id - WHERE - (septa_stops.id = $1 OR septa_stops.id = $2) - AND - service_id IN (SELECT service_id FROM septa_schedule_days WHERE date = $5) - AND - septa_stop_schedules.arrival_time > $3 - AND - septa_stop_schedules.arrival_time < $4 - ORDER BY arrival_time - ;"#) - .bind(&ids[0]) - .bind(&ids.get(1).unwrap_or(&0)) - .bind(&start_secs) - .bind(&end_secs) - .bind(&schedule_day_str) - .fetch_all(&mut **transaction) - .await?; - - let mut ntas: Vec = Vec::new(); - let mut live_map: HashMap = HashMap::new(); - - let lives: Vec = reqwest::get("https://www3.septa.org/api/v2/trips/?route_id=AIR,CHW,LAN,NOR,TRE,WIL,WAR,MED,PAO,FOX,WTR,CYN").await?.json().await?; - - for live in lives { - live_map.insert(live.route_id, NTALive { delay: live.delay, cancelled: live.status == "CANCELLED", next_stop: live.next_stop_name }); - } - - for row in rows { - ntas.push(NTAEntry { - route_id: row.0.clone(), - route_type: row.1, - color_hex: row.2, - trip_id: row.3, - arrival_time: row.4, - direction: row.5, - direction_destination: row.6, - route_name: row.7, - live: match live_map.get(&row.0) { - Some(x) => Some(x.clone()), - None => None - } - }); - } - - return Ok(NTAResult{ - station_name: stop_name, - arrivals: ntas - }); -} diff --git a/api/src/main.rs b/api/src/main.rs index 89fc80b..1082ead 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -1,6 +1,4 @@ -use actix_web::{get, web::{self, Data}, App, HttpResponse, HttpServer, Responder}; -use chrono::TimeDelta; -use database::get_nta_by_stop_id; +use actix_web::{get, web::Data, App, HttpResponse, HttpServer, Responder}; use env_logger::Env; use log::*; use dotenv::dotenv; @@ -23,22 +21,11 @@ async fn get_index() -> impl Responder { HttpResponse::Ok().body(templates::ContentTemplate { page_title: None, page_desc: None, - content: templates::IndexTemplate {} + content: templates::IndexTemplate {}, + load_time_ms: None }.render().unwrap()) } -#[get("/api/stop/{stop_id}/nta")] -async fn api_get_nta(state: Data>, path: web::Path) -> impl Responder { - let route_id = path.into_inner().split(',') .map(|s| s.parse::()) - .collect::, _>>().unwrap(); - let route_r = get_nta_by_stop_id(route_id, chrono::Utc::now(), chrono::Utc::now() + TimeDelta::minutes(30), &mut state.database.begin().await.unwrap()).await; - if let Ok(route) = route_r { - HttpResponse::Ok().json(route) - } else { - HttpResponse::InternalServerError().body(format!("Error {:?}", route_r.err())) - } -} - #[actix_web::main] async fn main() -> ::anyhow::Result<()> { env_logger::init_from_env(Env::default().default_filter_or("septastic_api=info")); @@ -47,8 +34,6 @@ async fn main() -> ::anyhow::Result<()> { let version: &str = option_env!("CARGO_PKG_VERSION").expect("Expected package version"); info!("Starting SEPTASTIC Server v{} (commit: {})", version, "NONE"); - info!("Connecting to postgres database"); - let connection_string = std::env::var("DB_CONNSTR").expect("Expected database connection string"); @@ -65,12 +50,10 @@ async fn main() -> ::anyhow::Result<()> { trip_tracking_service: tt_service }); - HttpServer::new(move || { App::new() .wrap(actix_cors::Cors::permissive()) .app_data(Data::new(state.clone())) - .service(api_get_nta) .service(controllers::route::api_get_route) .service(controllers::route::api_get_schedule) .service(controllers::route::get_route) diff --git a/api/src/services/trip_tracking.rs b/api/src/services/trip_tracking.rs index 24d0258..25e0d13 100644 --- a/api/src/services/trip_tracking.rs +++ b/api/src/services/trip_tracking.rs @@ -4,7 +4,7 @@ use std::sync::{Arc, Mutex}; use std::thread; use std::collections::HashMap; use std::time::Duration; -use log::{error, info}; +use log::error; use serde::{Serialize, Deserialize, Deserializer}; use libseptastic::stop_schedule::{LiveTrip, TripTracking}; diff --git a/api/src/templates.rs b/api/src/templates.rs index 194358f..67f57b8 100644 --- a/api/src/templates.rs +++ b/api/src/templates.rs @@ -9,13 +9,13 @@ pub struct ContentTemplate { pub content: T, pub page_title: Option, pub page_desc: Option, + pub load_time_ms: Option } #[derive(askama::Template)] #[template(path = "route.html")] pub struct RouteTemplate { pub route: libseptastic::route::Route, - pub directions: Vec, pub timetables: Vec, pub filter_stops: Option> } diff --git a/api/templates/layout.html b/api/templates/layout.html index d5e98f6..33854a0 100644 --- a/api/templates/layout.html +++ b/api/templates/layout.html @@ -51,8 +51,11 @@

SEPTASTIC!

- Copyright © Nicholas Orlowsky 2025 + Copyright © Nicholas Orlowsky 2025

+ {% if let Some(load_time) = load_time_ms %} +

Data loaded in {{ load_time }}ms

+ {% endif %}