Skip to content

Commit

Permalink
Full weather page
Browse files Browse the repository at this point in the history
  • Loading branch information
Celeo committed Mar 12, 2024
1 parent 51272c7 commit c0e52f9
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 8 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ anyhow = "1.0.79"
axum = "0.7.4"
chrono = "0.4.34"
clap = { version = "4.5.1", features = ["derive"] }
itertools = "0.12.1"
log = "0.4.20"
mini-moka = { version = "0.10.3", features = ["sync"] }
minijinja = "1.0.12"
Expand Down
7 changes: 2 additions & 5 deletions src/endpoints/homepage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use crate::{
shared::{AppError, AppState, CacheEntry, UserInfo, SESSION_USER_INFO_KEY},
utils::{parse_metar, parse_vatsim_timestamp},
utils::{parse_metar, parse_vatsim_timestamp, GENERAL_HTTP_CLIENT},
};
use anyhow::{anyhow, Result};
use axum::{extract::State, http::StatusCode, response::Html, routing::get, Router};
Expand Down Expand Up @@ -99,10 +99,7 @@ async fn snippet_weather(State(state): State<Arc<AppState>>) -> Result<Html<Stri
state.cache.invalidate(&cache_key);
}

let client = reqwest::ClientBuilder::new()
.user_agent("github.com/celeo/vzdv")
.build()?;
let resp = client
let resp = GENERAL_HTTP_CLIENT
.get(format!(
"https://metar.vatsim.net/{}",
state.config.airports.weather_for.join(",")
Expand Down
63 changes: 62 additions & 1 deletion src/endpoints/pilots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ use crate::{
shared::{
sql::INSERT_FEEDBACK, AppError, AppState, CacheEntry, UserInfo, SESSION_USER_INFO_KEY,
},
utils::{flashed_messages, simaware_data, GENERAL_HTTP_CLIENT},
utils::{flashed_messages, parse_metar, simaware_data, GENERAL_HTTP_CLIENT},
};
use anyhow::anyhow;
use axum::{
extract::State,
response::{Html, Redirect},
routing::{get, post},
Form, Router,
};
use itertools::Itertools;
use log::warn;
use minijinja::{context, Environment};
use serde::{Deserialize, Serialize};
use serde_json::json;
Expand Down Expand Up @@ -164,6 +167,59 @@ async fn page_flights(
Ok(Html(rendered))
}

/// Larger view of the weather.
async fn page_weather(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, AppError> {
// cache this endpoint's returned data for 5 minutes
let cache_key = "WEATHER_FULL";
if let Some(cached) = state.cache.get(&cache_key) {
let elapsed = Instant::now() - cached.inserted;
if elapsed.as_secs() < 300 {
return Ok(Html(cached.data));
}
state.cache.invalidate(&cache_key);
}

let resp = GENERAL_HTTP_CLIENT
.get(format!(
"https://metar.vatsim.net/{}",
state
.config
.airports
.all
.iter()
.map(|airport| &airport.code)
.join(",")
))
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow!("Got status {} from METAR API", resp.status().as_u16()).into());
}
let text = resp.text().await?;
let weather: Vec<_> = text
.split_terminator('\n')
.flat_map(|line| {
parse_metar(line).map_err(|e| {
let airport = line.split(' ').next().unwrap_or("Unknown");
warn!("Metar parsing failure for {airport}: {e}");
e
})
})
.collect();

let user_info: Option<UserInfo> = session.get(SESSION_USER_INFO_KEY).await.unwrap();
let template = state.templates.get_template("weather")?;
let rendered = template.render(context! { user_info, weather })?;
state
.cache
.insert(cache_key, CacheEntry::new(rendered.clone()));
Ok(Html(rendered))
}

/// Form for groups to submit requests for staff-ups.
async fn page_staffing_request(
State(state): State<Arc<AppState>>,
session: Session,
Expand All @@ -190,6 +246,7 @@ struct StaffingRequestForm {
comments: String,
}

/// Submit the staffing request form.
async fn page_staffing_request_post(
State(state): State<Arc<AppState>>,
session: Session,
Expand Down Expand Up @@ -292,12 +349,16 @@ pub fn router(templates: &mut Environment) -> Router<Arc<AppState>> {
include_str!("../../templates/staffing_request.jinja"),
)
.unwrap();
templates
.add_template("weather", include_str!("../../templates/weather.jinja"))
.unwrap();

Router::new()
.route("/pilots/feedback", get(page_feedback_form))
.route("/pilots/feedback", post(page_feedback_form_post))
.route("/pilots/airports", get(page_airports))
.route("/pilots/flights", get(page_flights))
.route("/pilots/weather", get(page_weather))
.route("/pilots/staffing_request", get(page_staffing_request))
.route("/pilots/staffing_request", post(page_staffing_request_post))
}
6 changes: 5 additions & 1 deletion src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,16 @@ pub enum WeatherConditions {
pub struct AirportWeather<'a> {
pub name: &'a str,
pub conditions: WeatherConditions,
pub visibility: u8,
pub ceiling: u16,
pub raw: &'a str,
}

/// Parse a METAR to determine if conditions are VMC or IMC.
pub fn parse_metar(line: &str) -> Result<AirportWeather> {
let parts: Vec<_> = line.split(' ').collect();
let airport = parts.first().ok_or_else(|| anyhow!("Blank metar?"))?;
let mut ceiling = 3_001;
let mut ceiling = 3_456;
for part in &parts {
if part.starts_with("BKN") || part.starts_with("OVC") {
ceiling = part
Expand Down Expand Up @@ -91,6 +93,8 @@ pub fn parse_metar(line: &str) -> Result<AirportWeather> {
Ok(AirportWeather {
name: airport,
conditions,
visibility,
ceiling,
raw: line,
})
}
Expand Down
1 change: 1 addition & 0 deletions templates/_layout.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/pilots/airports">Airports</a></li>
<li><a class="dropdown-item" href="/pilots/flights">Flights</a></li>
<li><a class="dropdown-item" href="/pilots/weather">Weather</a></li>
<li><a class="dropdown-item" href="/pilots/staffing_request">Staffing Request</a></li>
<li><hr class="dropdown-divider"></li>
<li>
Expand Down
2 changes: 1 addition & 1 deletion templates/snippets/weather.jinja
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<h4>Weather</h4>
<h4><a href="/pilots/weather">Weather</a></h4>
<ul>
{% for airport in weather %}
<li title="{{ airport.raw }}">
Expand Down
48 changes: 48 additions & 0 deletions templates/weather.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{% extends "_layout" %}

{% block title %}Weather | {{ super() }}{% endblock %}

{% block body %}

<h2>Weather</h2>

<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Visibility</th>
<th>Ceiling</th>
<th>Conditions</th>
<th>Full</th>
</tr>
</thead>
<tbody>
{% for airport in weather %}
<tr>
<td>{{ airport.name }}</td>
<td>{{ airport.visibility }}</td>
<td>
{% if airport.ceiling == 3456 %}
Clear
{% else %}
{{ airport.ceiling }}
{% endif %}
</td>
<td>
{% if airport.conditions == 'VFR' %}
<span class="badge rounded-pill text-bg-success">{{ airport.conditions }}</span>
{% elif airport.conditions == 'MVFR' %}
<span class="badge rounded-pill text-bg-info">{{ airport.conditions }}</span>
{% elif airport.conditions == 'IFR' %}
<span class="badge rounded-pill text-bg-error">{{ airport.conditions }}</span>
{% else %}
<span class="badge rounded-pill" style="background-color: purple;">{{ airport.conditions }}</span>
{% endif %}
</td>
<td>{{ airport.raw }}</td>
</tr>
{% endfor %}
</tbody>
</table>

{% endblock %}

0 comments on commit c0e52f9

Please sign in to comment.