Add widescreen setting and universal time

This commit is contained in:
Nicholas Orlowsky 2025-09-25 22:08:59 -04:00
parent 775d6c2899
commit ce912d4b85
No known key found for this signature in database
GPG key ID: A9F3BA4C0AA7A70B
7 changed files with 295 additions and 64 deletions

View file

@ -1,4 +1,5 @@
use actix_web::{get, web::{Data, self}, HttpResponse, Responder};
use actix_web::{get, web::{self, Data}, HttpRequest, HttpResponse, Responder};
use anyhow::anyhow;
use std::{time::Instant, sync::Arc};
use libseptastic::{route::RouteType, stop_schedule::Trip};
use serde::{Serialize, Deserialize};
@ -8,27 +9,33 @@ use crate::AppState;
use crate::database;
#[get("/routes")]
async fn get_routes_html(state: Data<Arc<AppState>>) -> impl Responder {
let start_time = Instant::now();
async fn get_routes_html(req: HttpRequest, state: Data<Arc<AppState>>) -> impl Responder {
crate::perform_action(req, move || {
let statex = state.clone();
async move {
let start_time = Instant::now();
let all_routes = database::get_all_routes(&mut state.database.begin().await.unwrap()).await.unwrap();
let all_routes = database::get_all_routes(&mut statex.database.begin().await?).await?;
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();
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: Some(String::from("SEPTASTIC | Routes")),
page_desc: Some(String::from("All SEPTA routes.")),
content: crate::templates::RoutesTemplate {
rr_routes,
subway_routes,
trolley_routes,
bus_routes,
},
load_time_ms: Some(start_time.elapsed().as_millis())
}.render().unwrap())
Ok(crate::templates::ContentTemplate {
page_title: Some(String::from("SEPTASTIC | Routes")),
page_desc: Some(String::from("All SEPTA routes.")),
widescreen: false,
content: crate::templates::RoutesTemplate {
rr_routes,
subway_routes,
trolley_routes,
bus_routes,
},
load_time_ms: Some(start_time.elapsed().as_millis())
})
}
}).await
}
#[get("/routes.json")]
@ -37,7 +44,7 @@ async fn get_routes_json(state: Data<Arc<AppState>>) -> impl Responder {
HttpResponse::Ok().json(all_routes)
}
#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct RouteQueryParams {
#[serde(default)] // Optional: handle missing parameters with a default value
stops: Option<String>,
@ -67,39 +74,43 @@ async fn get_route_info(route_id: String, state: Data<Arc<AppState>>) -> ::anyho
}
#[get("/route/{route_id}")]
async fn get_route(state: Data<Arc<AppState>>, info: web::Query<RouteQueryParams>, path: web::Path<String>) -> impl Responder {
let start_time = Instant::now();
async fn get_route(state: Data<Arc<AppState>>, req: HttpRequest, info: web::Query<RouteQueryParams>, path: web::Path<String>) -> impl Responder {
crate::perform_action(req, move || {
let pathx = path.clone();
let infox = info.clone();
let statex = state.clone();
async move {
let mut filters: Option<Vec<i64>> = None;
if let Some (stops_v) = infox.stops.clone() {
let mut items = Vec::new();
let mut filters: Option<Vec<i64>> = None;
if let Some (stops_v) = info.stops.clone() {
let mut items = Vec::new();
for sid in stops_v.split(",") {
items.push(sid.parse::<i64>().unwrap());
for sid in stops_v.split(",") {
items.push(sid.parse::<i64>().unwrap());
}
filters = Some(items);
}
filters = Some(items);
}
let route_id = path.into_inner();
let route_info_r = get_route_info(route_id.clone(), state).await;
let load_time_ms = Some(start_time.elapsed().as_millis());
let route_id = pathx;
let route_info_r = get_route_info(route_id.clone(), statex.clone()).await;
if let Ok(route_info) = route_info_r {
let timetables = crate::templates::build_timetables(route_info.directions, route_info.schedule);
if let Ok(route_info) = route_info_r {
let timetables = crate::templates::build_timetables(route_info.directions, route_info.schedule);
HttpResponse::Ok().body(crate::templates::ContentTemplate {
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,
timetables,
filter_stops: filters.clone()
},
load_time_ms
}.render().unwrap())
} else {
HttpResponse::InternalServerError().body("Error")
}
Ok(crate::templates::ContentTemplate {
widescreen: false,
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,
timetables,
filter_stops: filters.clone()
},
load_time_ms: None
})
} else {
Err(anyhow!("test"))
}
}}).await
}
#[get("/route/{route_id}.json")]

View file

@ -1,29 +1,75 @@
use actix_web::{get, web::Data, App, HttpResponse, HttpServer, Responder};
use actix_web::{cookie::Cookie, get, web::Data, App, HttpRequest, HttpResponse, HttpServer, Responder};
use env_logger::Env;
use log::*;
use dotenv::dotenv;
use serde::Deserialize;
use services::trip_tracking::{self};
use std::sync::Arc;
use templates::ContentTemplate;
use std::{sync::Arc, time::Instant};
use askama::Template;
mod database;
mod services;
mod controllers;
mod templates;
mod middleware;
pub struct AppState {
database: ::sqlx::postgres::PgPool,
trip_tracking_service: services::trip_tracking::TripTrackingService
}
#[derive(Deserialize)]
struct LocalStateQuery {
pub widescreen: Option<bool>
}
pub async fn perform_action<F, Fut, T >(req: HttpRequest, func: F) -> impl Responder
where T: Template,
F: Fn() -> Fut,
Fut: Future<Output = anyhow::Result<ContentTemplate<T>>> + 'static {
let start_time = Instant::now();
let mut enable_widescreen = false;
if let Some(widescreen_set) = req.cookie("widescreen") {
enable_widescreen = widescreen_set.value() == "true";
}
let query_params = actix_web::web::Query::<LocalStateQuery>::from_query(req.query_string()).unwrap();
if let Some(set_widescreen) = query_params.widescreen {
enable_widescreen = set_widescreen;
}
let x = func().await;
match x {
Ok(mut y) => {
y.widescreen = enable_widescreen;
y.load_time_ms = Some(start_time.elapsed().as_nanos());
let mut cookie = Cookie::new("widescreen", y.widescreen.to_string());
cookie.set_path("/");
HttpResponse::Ok()
.cookie(cookie)
.body(y.render().unwrap())
},
Err(_) => {
HttpResponse::InternalServerError().body("Error")
}
}
}
#[get("/")]
async fn get_index() -> impl Responder {
HttpResponse::Ok().body(templates::ContentTemplate {
async fn get_index(req: HttpRequest) -> impl Responder {
perform_action(req, move || async {
Ok(templates::ContentTemplate {
page_title: None,
page_desc: None,
content: templates::IndexTemplate {},
load_time_ms: None
}.render().unwrap())
load_time_ms: None,
widescreen: false
})
}).await
}
#[actix_web::main]
@ -53,6 +99,7 @@ async fn main() -> ::anyhow::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(actix_cors::Cors::permissive())
.wrap(actix_web::middleware::from_fn(middleware::local_state::local_state))
.app_data(Data::new(state.clone()))
.service(controllers::route::api_get_route)
.service(controllers::route::api_get_schedule)

View file

@ -9,7 +9,8 @@ pub struct ContentTemplate<T: askama::Template> {
pub content: T,
pub page_title: Option<String>,
pub page_desc: Option<String>,
pub load_time_ms: Option<u128>
pub load_time_ms: Option<u128>,
pub widescreen: bool
}
#[derive(askama::Template)]
@ -127,6 +128,20 @@ pub fn build_timetables(
}
mod filters {
pub fn format_load_time(
nanos: &u128,
_: &dyn askama::Values,
) -> askama::Result<String> {
if *nanos >= 1000000000 {
return Ok(format!("{}s", (nanos/1000000000)));
} else if *nanos >= 1000000 {
return Ok(format!("{}ms", nanos/1000000));
} if *nanos >= 1000 {
return Ok(format!("{}us", nanos/1000));
} else {
return Ok(format!("{}ns", nanos));
}
}
pub fn format_time(
seconds_since_midnight: &i64,
_: &dyn askama::Values,