Expand parser, fixed comments
This commit is contained in:
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!(
|
||||
|
||||
Reference in New Issue
Block a user