Expand parser, fixed comments

This commit is contained in:
2025-04-07 22:58:52 -04:00
parent b770c343ec
commit ce6d02be34
10 changed files with 172 additions and 24 deletions

View File

@@ -1,3 +1,4 @@
use std::sync::OnceLock;
use actix_web::{
post, web, HttpResponse, ResponseError,
cookie::{Cookie, time::Duration},
@@ -12,23 +13,36 @@ use crate::{
use crate::auth::{Auth, DEFAULT_SESSION_TTL};
#[post("/register")]
async fn register(user: web::Json<RegisterRequest>) -> HttpResponse {
let register_user = user.0;
let insert_user: User = match register_user.to_user() {
async fn register(user: web::Json<RegisterRequest>, req: HttpRequest) -> HttpResponse {
let register_user = user.into_inner();
let email = register_user.email.clone();
let ip_address = req.peer_addr().unwrap().ip().to_string();
let mut insert_user: User = match register_user.to_user() {
Ok(user) => user,
Err(err) => return ResponseError::error_response(&err),
};
match insert_user.insert().await {
Ok(user) => {
let response: UserResponse = user.into();
log::trace!("Registered user '{}'", response.email);
log::info!(
"Successful user registration [Email: {}] [IP Address: {}]",
email,
ip_address
);
HttpResponse::Created().json(response)
}
Err(err) => {
// Obfuscate the service error message to prevent leaking database details
if err.status == 409 {
log::warn!(
"Duplicate user registration attempt [Email: {}] [IP Address: {}]",
email,
ip_address
);
HttpResponse::Conflict().finish()
} else {
log::error!("attemptFailed to register user [Email: {}]: {}", email, err);
ResponseError::error_response(&err)
}
}
@@ -51,28 +65,54 @@ async fn login(request: web::Json<LoginRequest>, req: HttpRequest) -> HttpRespon
let session_cookie = session.to_cookie();
// Save the session to the database
if let Err(err) = session.store().await {
log::error!("Failed to store session");
log::error!(
"Login attempt failure [Email: {}] [IP Address: {}]: {}",
email,
ip_address,
err
);
return ResponseError::error_response(&Error::new(500, err.to_string()));
}
log::info!(
"Successful login attempt [Email: {}] [IP Address: {}]",
email,
ip_address
);
HttpResponse::Ok().cookie(session_cookie).finish()
} else {
log::error!("Invalid login attempt for {}", email);
log::error!(
"Invalid login attempt [Email: {}] [IP Address: {}]",
email,
ip_address
);
HttpResponse::Unauthorized().finish()
}
}
#[post("/logout")]
async fn logout(req: HttpRequest, _auth: Auth) -> HttpResponse {
async fn logout(req: HttpRequest, auth: Auth) -> HttpResponse {
let email = auth.user.email;
let ip_address = req.peer_addr().unwrap().ip().to_string();
// Delete the session from the store
match req.cookie(SESSION_COOKIE_NAME) {
Some(cookie) => {
let session_id = cookie.value().to_string();
if let Err(err) = Session::delete(&session_id).await {
log::error!("Failed to delete session");
log::error!(
"Logout attempt failure [Email: {}] [IP Address: {}]: {}",
email,
ip_address,
err
);
return ResponseError::error_response(&Error::new(500, err.to_string()));
}
}
None => {
log::error!(
"Invalid logout attempt [Email: {}] [IP Address: {}]",
email,
ip_address
);
return ResponseError::error_response(&Error::new(400, "Invalid session".to_string()));
}
}
@@ -84,6 +124,11 @@ async fn logout(req: HttpRequest, _auth: Auth) -> HttpResponse {
.http_only(true)
.finish();
log::info!(
"Successful logout attempt [Email: {}] [IP Address: {}]",
email,
ip_address
);
HttpResponse::Ok().cookie(session_cookie).finish()
}

View File

@@ -93,12 +93,26 @@ impl Session {
None => DEFAULT_SESSION_TTL,
};
let ttl = expires_at - Utc::now().timestamp();
Cookie::build(SESSION_COOKIE_NAME, self.session_id.clone())
let mut cookie = Cookie::build(SESSION_COOKIE_NAME, self.session_id.clone())
.path("/")
.max_age(Duration::seconds(ttl))
// TODO: enable secure and http_only
// .secure(true)
// .http_only(true)
.finish()
.secure(true)
.http_only(true)
.finish();
if let Ok(environment) = std::env::var("ENVIRONMENT") {
if environment == "development" || environment == "dev" {
log::debug!(
"Development cookie [Email: {}]: {}",
self.email,
self.session_id
);
cookie.set_secure(false);
cookie.set_http_only(false);
}
}
cookie
}
}

View File

@@ -24,16 +24,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
let port = env::var("API_PORT").unwrap_or("5000".to_string());
// Initialize admin user
let admin_username = env::var("ADMIN_USERNAME");
let admin_email = env::var("ADMIN_EMAIL");
let admin_password = env::var("ADMIN_PASSWORD");
if admin_username.is_ok() && admin_password.is_ok() {
let username = admin_username.unwrap();
if User::select(&username).await.is_none() {
if admin_email.is_ok() && admin_password.is_ok() {
let email = admin_email.unwrap();
if User::select(&email).await.is_none() {
log::debug!("Creating default administrator");
let password = admin_password.unwrap();
let password_hash = hash(&password)?;
let admin_user = User {
email: username,
email,
password_hash,
role: ADMIN_ROLE.to_string(),
first_name: "Admin".to_string(),

View File

@@ -82,6 +82,20 @@ pub struct QualityControlFlags {
pub no_significant_change: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temporary_change: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rvr_missing: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub precipication_identifier_information_not_available: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub precipication_information_not_available: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub freezing_rain_information_not_available: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thunderstorm_information_not_available: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility_at_secondary_location_not_available: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub sky_condition_at_secondary_location_not_available: Option<String>,
}
impl Default for QualityControlFlags {
@@ -94,6 +108,13 @@ impl Default for QualityControlFlags {
corrected: None,
no_significant_change: None,
temporary_change: None,
rvr_missing: None,
precipication_identifier_information_not_available: None,
precipication_information_not_available: None,
freezing_rain_information_not_available: None,
thunderstorm_information_not_available: None,
visibility_at_secondary_location_not_available: None,
sky_condition_at_secondary_location_not_available: None,
}
}
}
@@ -202,6 +223,11 @@ impl Metar {
));
}
// Remove METAR at start of text
if metar_parts[0].to_string() == "METAR".to_string() {
metar_parts.remove(0);
}
// Station Identifier
metar.station_id = metar_parts[0].to_string();
metar_parts.remove(0);
@@ -602,6 +628,36 @@ impl Metar {
metar.quality_control_flags.auto_station_with_precipication = Some(true);
} else if remark == "$" {
metar.quality_control_flags.maintenance_indicator_on = Some(true);
} else if remark == "PNO" {
metar
.quality_control_flags
.precipication_information_not_available = Some(true);
} else if remark == "RVRNO" {
metar.quality_control_flags.rvr_missing = Some(true);
} else if remark == "PWINO" {
metar
.quality_control_flags
.precipication_identifier_information_not_available = Some(true);
} else if remark == "FZRANO" {
metar
.quality_control_flags
.freezing_rain_information_not_available = Some(true);
} else if remark == "TSNO" {
metar
.quality_control_flags
.thunderstorm_information_not_available = Some(true);
} else if remark == "VISNO" {
let location = metar_parts[0];
metar_parts.remove(0);
metar
.quality_control_flags
.visibility_at_secondary_location_not_available = Some(location.to_string());
} else if remark == "CHINO" {
let location = metar_parts[0];
metar_parts.remove(0);
metar
.quality_control_flags
.sky_condition_at_secondary_location_not_available = Some(location.to_string());
} else if slp_re.is_match(remark) {
let slp = slp_re.captures(remark).unwrap();
let sea_level_pressure = slp[1].parse::<f64>().unwrap();
@@ -853,3 +909,18 @@ impl Metar {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_metar() {
let metar_string = "METAR KABC 121755Z AUTO 21016G24KT 180V240 1SM R11/P6000FT -RA BR BKN015 OVC025 06/04 A2990
RMK AO2 PK WND 20032/25 WSHFT 1715 VIS 3/4V1 1/2 VIS 3/4 RWY11 RAB07 CIG 013V017 CIG 017 RWY11 PRESFR
SLP125 P0003 60009 T00640036 10066 21012 58033 TSNO $".to_string();
let metar = Metar::parse(&metar_string).unwrap();
dbg!(&metar);
}
}

View File

@@ -96,6 +96,20 @@ impl User {
user
}
pub async fn count() -> i64 {
let pool = db::pool();
sqlx::query_scalar(&format!(
r#"
SELECT COUNT(*) FROM {}
"#,
TABLE_NAME
))
.fetch_one(pool)
.await
.unwrap_or_else(|_| 0)
}
pub async fn insert(&self) -> ApiResult<User> {
let pool = db::pool();
let user: User = sqlx::query_as::<_, Self>(&format!(