Skip to content

Commit

Permalink
Staffing request
Browse files Browse the repository at this point in the history
  • Loading branch information
Celeo committed Mar 12, 2024
1 parent f3d21fb commit f79074a
Show file tree
Hide file tree
Showing 6 changed files with 206 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/endpoints/homepage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tower_sessions::Session;
use vatsim_utils::live_api::Vatsim;

/// Homepage.
async fn handler_home(
async fn page_home(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, StatusCode> {
Expand Down Expand Up @@ -208,7 +208,7 @@ pub fn router(templates: &mut Environment) -> Router<Arc<AppState>> {
.unwrap();

Router::new()
.route("/", get(handler_home))
.route("/", get(page_home))
.route(
"/snippets/online/controllers",
get(snippet_online_controllers),
Expand Down
4 changes: 2 additions & 2 deletions src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub mod pilots;
/// 404 not found page.
///
/// Redirected to whenever the router cannot find a valid handler for the requested path.
async fn handler_404(
async fn page_404(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, AppError> {
Expand All @@ -30,5 +30,5 @@ pub fn router(templates: &mut Environment) -> Router<Arc<AppState>> {
.add_template("404", include_str!("../../templates/404.jinja"))
.unwrap();

Router::new().route("/404", get(handler_404))
Router::new().route("/404", get(page_404))
}
141 changes: 129 additions & 12 deletions src/endpoints/pilots.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
shared::{
sql::INSERT_FEEDBACK, AppError, AppState, CacheEntry, UserInfo, SESSION_USER_INFO_KEY,
},
utils::{flashed_messages, simaware_data},
utils::{flashed_messages, simaware_data, GENERAL_HTTP_CLIENT},
};
use axum::{
extract::State,
Expand All @@ -12,6 +12,7 @@ use axum::{
};
use minijinja::{context, Environment};
use serde::{Deserialize, Serialize};
use serde_json::json;
use std::{sync::Arc, time::Instant};
use thousands::Separable;
use tower_sessions::Session;
Expand All @@ -26,7 +27,7 @@ async fn page_feedback_form(
) -> Result<Html<String>, AppError> {
let user_info: Option<UserInfo> = session.get(SESSION_USER_INFO_KEY).await.unwrap();
let flashed_messages = flashed_messages::drain_flashed_messages(session).await?;
let template = state.templates.get_template("pilot_feedback").unwrap();
let template = state.templates.get_template("feedback").unwrap();
let rendered = template
.render(context! { user_info, flashed_messages })
.unwrap();
Expand All @@ -42,7 +43,7 @@ struct FeedbackForm {
}

/// Submit the feedback form.
async fn post_feedback_form(
async fn page_feedback_form_post(
State(state): State<Arc<AppState>>,
session: Session,
Form(feedback): Form<FeedbackForm>,
Expand Down Expand Up @@ -77,7 +78,7 @@ async fn post_feedback_form(
}

/// Table of all the airspace's airports.
async fn handler_airports(
async fn page_airports(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, AppError> {
Expand All @@ -89,7 +90,7 @@ async fn handler_airports(
}

/// Table of all airspace-relevant flights.
async fn handler_flights(
async fn page_flights(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, AppError> {
Expand Down Expand Up @@ -163,24 +164,140 @@ async fn handler_flights(
Ok(Html(rendered))
}

async fn page_staffing_request(
State(state): State<Arc<AppState>>,
session: Session,
) -> Result<Html<String>, AppError> {
let user_info: Option<UserInfo> = session.get(SESSION_USER_INFO_KEY).await.unwrap();
let flashed_messages = flashed_messages::drain_flashed_messages(session).await?;
let template = state.templates.get_template("staffing_request").unwrap();
let rendered = template
.render(context! { user_info, flashed_messages })
.unwrap();
Ok(Html(rendered))
}

#[derive(Debug, Deserialize)]
struct StaffingRequestForm {
departure: String,
dt_start: String,
pilot_count: i16,
contact: String,
arrival: String,
dt_end: String,
banner: String,
organization: String,
comments: String,
}

async fn page_staffing_request_post(
State(state): State<Arc<AppState>>,
session: Session,
Form(staffing_request): Form<StaffingRequestForm>,
) -> Result<Redirect, AppError> {
let user_info: Option<UserInfo> = session.get(SESSION_USER_INFO_KEY).await.unwrap();
if let Some(user_info) = user_info {
let resp = GENERAL_HTTP_CLIENT
.post(&state.config.webhooks.staffing_request)
.json(&json!({
"content": "",
"embeds": [{
"title": "New staffing request",
"fields": [
{
"name": "From",
"value": format!("{} {} ({})", user_info.first_name, user_info.last_name, user_info.cid)
},
{
"name": "departure",
"value": staffing_request.departure
},
{
"name": "arrival",
"value": staffing_request.arrival
},
{
"name": "dt_start",
"value": staffing_request.dt_start
},
{
"name": "dt_end",
"value": staffing_request.dt_end
},
{
"name": "pilot_count",
"value": staffing_request.pilot_count
},
{
"name": "contact",
"value": staffing_request.contact
},
{
"name": "banner",
"value": staffing_request.banner
},
{
"name": "organization",
"value": staffing_request.organization
},
{
"name": "comments",
"value": staffing_request.comments
}
]
}]
}))
.send()
.await?;
if resp.status().is_success() {
flashed_messages::push_flashed_message(
session,
flashed_messages::FlashedMessageLevel::Success,
"Request submitted",
)
.await?;
} else {
flashed_messages::push_flashed_message(
session,
flashed_messages::FlashedMessageLevel::Error,
"The message could not be processed. You may want to contact the EC (or WM).",
)
.await?;
}
} else {
flashed_messages::push_flashed_message(
session,
flashed_messages::FlashedMessageLevel::Error,
"You must be logged in to submit a request",
)
.await?;
}
Ok(Redirect::to("/pilots/staffing_request"))
}

/// This file's routes and templates.
pub fn router(templates: &mut Environment) -> Router<Arc<AppState>> {
templates
.add_template(
"pilot_feedback",
include_str!("../../templates/pilot_feedback.jinja"),
)
.add_template("feedback", include_str!("../../templates/feedback.jinja"))
.unwrap();
templates
.add_template("airports", include_str!("../../templates/airports.jinja"))
.unwrap();
templates
.add_template("flights", include_str!("../../templates/flights.jinja"))
.unwrap();
templates
.add_template(
"staffing_request",
include_str!("../../templates/staffing_request.jinja"),
)
.unwrap();

Router::new()
.route("/pilots/feedback", get(page_feedback_form))
.route("/pilots/feedback", post(post_feedback_form))
.route("/pilots/airports", get(handler_airports))
.route("/pilots/flights", get(handler_flights))
.route("/pilots/feedback", post(page_feedback_form_post))
.route("/pilots/airports", get(page_airports))
.route("/pilots/flights", get(page_flights))
.route("/pilots/staffing_request", get(page_staffing_request))
.route("/pilots/staffing_request", post(page_staffing_request_post))
}
16 changes: 12 additions & 4 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,23 @@ use std::collections::HashMap;

use anyhow::{anyhow, Result};
use chrono::{DateTime, NaiveDateTime, TimeZone, Utc};
use once_cell::sync::Lazy;
use reqwest::ClientBuilder;
use serde::{Deserialize, Serialize};

pub mod auth;
pub mod flashed_messages;

/// HTTP client for making external requests.
///
/// Include an HTTP Agent of the project's repo for contact.
pub static GENERAL_HTTP_CLIENT: Lazy<reqwest::Client> = Lazy::new(|| {
ClientBuilder::new()
.user_agent("github.com/celeo/vzdv")
.build()
.expect("Could not construct HTTP client")
});

/// Parse a VATSIM timestamp into a `chrono::DateTime`.
pub fn parse_vatsim_timestamp(stamp: &str) -> Result<DateTime<Utc>> {
let naive = NaiveDateTime::parse_from_str(stamp, "%Y-%m-%dT%H:%M:%S%.fZ")?;
Expand Down Expand Up @@ -99,10 +110,7 @@ pub async fn simaware_data() -> Result<HashMap<u64, String>> {
}

let mut mapping = HashMap::new();
let client = ClientBuilder::new()
.user_agent("github.com/celeo/vzdv")
.build()?;
let data: TopLevel = client
let data: TopLevel = GENERAL_HTTP_CLIENT
.get("https://r2.simaware.ca/api/livedata/data.json")
.send()
.await?
Expand Down
File renamed without changes.
61 changes: 61 additions & 0 deletions templates/staffing_request.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{% extends "layout" %}

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

{% block body %}

<h2>Staffing Request</h2>

{% if not user_info or not user_info.cid %}
<h5>You must be <a href="/auth/log_in">logged in</a> to submit feedback</h5>
{% else %}
<form action="/pilots/staffing_request" method="POST">
<div class="row mb-2">
<div class="col">
<div class="mb-2">
<label for="departure">Departure Airport</label>
<input type="text" class="form-control" id="departure" name="departure" tabindex="1" required>
</div>
<div class="mb-2">
<label for="dt_start">Start date & time (Zulu)</label>
<input type="text" class="form-control" id="dt_start" name="dt_start" placeholder="01/15/2024 14:00" tabindex="3" required>
</div>
<div class="mb-2">
<label for="pilot_count">Number of Pilots (estimated)</label>
<input type="number" class="form-control" id="pilot_count" name="pilot_count" min="1" max="999" tabindex="5" required>
</div>
<div class="mb-2">
<label for="contact">Contact Info (homepage, email, Discord id, etc.)</label>
<input type="text" class="form-control" id="contact" name="contact" tabindex="7" required>
</div>
</div>
<div class="col">
<div class="mb-2">
<label for="arrival">Arrival Airport</label>
<input type="text" class="form-control" id="arrival" name="arrival" tabindex="2" required>
</div>
<div class="mb-2">
<label for="dt_end">End date & time (Zulu)</label>
<input type="text" class="form-control" id="dt_end" name="dt_end" placeholder="01/15/2024 14:00" tabindex="4" required>
</div>
<div class="mb-2">
<label for="banner">Banner URL (if you have one)</label>
<input type="text" class="form-control" id="banner" name="banner" tabindex="6">
</div>
<div class="mb-2">
<label for="organization">Organization (if part of one)</label>
<input type="text" class="form-control" id="organization" name="organization" tabindex="8">
</div>
</div>
</div>
<div class="row mb-4">
<div class="col">
<label for="comments">Any additional comments</label>
<textarea name="comments" id="comments" class="form-control" style="height: 5rem" tabindex="9"></textarea>
</div>
</div>
<button class="btn btn-primary" type="submit" tabindex="10">Submit</button>
</form>
{% endif %}

{% endblock %}

0 comments on commit f79074a

Please sign in to comment.