Formatting
This commit is contained in:
@@ -2,10 +2,10 @@ use std::future::Future;
|
|||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
use super::{SESSION_COOKIE_NAME, Session};
|
use super::{SESSION_COOKIE_NAME, Session};
|
||||||
|
use crate::account::user::User;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use actix_web::{Error as ActixError, FromRequest, HttpRequest, dev::Payload, http};
|
use actix_web::{Error as ActixError, FromRequest, HttpRequest, dev::Payload, http};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::account::user::User;
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Auth {
|
pub struct Auth {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use argon2::{
|
use argon2::{
|
||||||
password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHash, PasswordHasher,
|
Argon2, PasswordHash, PasswordHasher, PasswordVerifier,
|
||||||
PasswordVerifier,
|
password_hash::{SaltString, rand_core::OsRng},
|
||||||
};
|
};
|
||||||
use rand::distr::Alphanumeric;
|
use rand::distr::Alphanumeric;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|||||||
@@ -2,16 +2,16 @@ use crate::{
|
|||||||
account::{SESSION_COOKIE_NAME, Session, verify_hash},
|
account::{SESSION_COOKIE_NAME, Session, verify_hash},
|
||||||
error::Error,
|
error::Error,
|
||||||
};
|
};
|
||||||
use actix_web::{HttpRequest, HttpResponse, ResponseError, get, post, put, web, delete};
|
use actix_web::{HttpRequest, HttpResponse, ResponseError, delete, get, post, put, web};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
use utoipa_actix_web::scope;
|
use utoipa_actix_web::scope;
|
||||||
use utoipa_actix_web::service_config::ServiceConfig;
|
use utoipa_actix_web::service_config::ServiceConfig;
|
||||||
|
|
||||||
use crate::account::email_token::{EmailToken, send_confirm_email, send_password_reset_email};
|
use crate::account::email_token::{EmailToken, send_confirm_email, send_password_reset_email};
|
||||||
use crate::account::{Auth, csprng};
|
|
||||||
use crate::account::user::{LoginRequest, RegisterRequest, UpdateUser, User, UserResponse};
|
use crate::account::user::{LoginRequest, RegisterRequest, UpdateUser, User, UserResponse};
|
||||||
use crate::account::user_favorites::UserFavorite;
|
use crate::account::user_favorites::UserFavorite;
|
||||||
|
use crate::account::{Auth, csprng};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
tag = "account",
|
tag = "account",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use serde::Deserialize;
|
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::error::ApiResult;
|
use crate::error::ApiResult;
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
const TABLE_NAME: &str = "user_airport_favorites";
|
const TABLE_NAME: &str = "user_airport_favorites";
|
||||||
|
|
||||||
@@ -23,10 +23,7 @@ impl UserFavorite {
|
|||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let favorites = user_favorites
|
let favorites = user_favorites.iter().map(|uf| uf.icao.clone()).collect();
|
||||||
.iter()
|
|
||||||
.map(|uf| uf.icao.clone())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(favorites)
|
Ok(favorites)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -561,11 +561,12 @@ impl Airport {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for chunk in airport_rows.chunks(chunk_size) {
|
for chunk in airport_rows.chunks(chunk_size) {
|
||||||
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
|
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(format!(
|
||||||
format!("INSERT INTO {} (icao, iata, local, name, category, \
|
"INSERT INTO {} (icao, iata, local, name, category, \
|
||||||
iso_country, iso_region, municipality, elevation_ft, \
|
iso_country, iso_region, municipality, elevation_ft, \
|
||||||
longitude, latitude, geometry, has_tower, has_beacon, public) ", TABLE_NAME),
|
longitude, latitude, geometry, has_tower, has_beacon, public) ",
|
||||||
);
|
TABLE_NAME
|
||||||
|
));
|
||||||
query_builder.push_values(chunk, |mut b, row| {
|
query_builder.push_values(chunk, |mut b, row| {
|
||||||
b.push_bind(&row.icao)
|
b.push_bind(&row.icao)
|
||||||
.push_bind(&row.iata)
|
.push_bind(&row.iata)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use futures_util::stream::StreamExt as _;
|
use futures_util::stream::StreamExt as _;
|
||||||
|
|
||||||
|
use crate::account::ADMIN_ROLE;
|
||||||
use crate::airports::{AirportQuery, UpdateAirport};
|
use crate::airports::{AirportQuery, UpdateAirport};
|
||||||
use crate::{
|
use crate::{
|
||||||
account::{Auth, verify_role},
|
account::{Auth, verify_role},
|
||||||
@@ -11,7 +12,6 @@ use actix_web::{HttpRequest, HttpResponse, ResponseError, delete, get, post, put
|
|||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
use utoipa_actix_web::scope;
|
use utoipa_actix_web::scope;
|
||||||
use utoipa_actix_web::service_config::ServiceConfig;
|
use utoipa_actix_web::service_config::ServiceConfig;
|
||||||
use crate::account::ADMIN_ROLE;
|
|
||||||
|
|
||||||
#[derive(ToSchema)]
|
#[derive(ToSchema)]
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
|
|||||||
@@ -139,7 +139,9 @@ impl From<s3::error::S3Error> for Error {
|
|||||||
s3::error::S3Error::FromUtf8(err) => {
|
s3::error::S3Error::FromUtf8(err) => {
|
||||||
Self::new(500, format!("Unknown s3 from utf8 error: {:?}", err))
|
Self::new(500, format!("Unknown s3 from utf8 error: {:?}", err))
|
||||||
}
|
}
|
||||||
s3::error::S3Error::FmtError(err) => Self::new(500, format!("Unknown s3 fmt error: {:?}", err)),
|
s3::error::S3Error::FmtError(err) => {
|
||||||
|
Self::new(500, format!("Unknown s3 fmt error: {:?}", err))
|
||||||
|
}
|
||||||
s3::error::S3Error::HeaderToStr(err) => {
|
s3::error::S3Error::HeaderToStr(err) => {
|
||||||
Self::new(500, format!("Unknown s3 header to str error: {:?}", err))
|
Self::new(500, format!("Unknown s3 header to str error: {:?}", err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::account::{hash, ADMIN_ROLE};
|
use crate::account::User;
|
||||||
|
use crate::account::{ADMIN_ROLE, hash};
|
||||||
use crate::http_client::HttpClient;
|
use crate::http_client::HttpClient;
|
||||||
use actix_cors::Cors;
|
use actix_cors::Cors;
|
||||||
use actix_web::{App, HttpServer, middleware::Logger, web};
|
use actix_web::{App, HttpServer, middleware::Logger, web};
|
||||||
@@ -9,7 +10,6 @@ use utoipa::openapi::SecurityRequirement;
|
|||||||
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
|
||||||
use utoipa_actix_web::{AppExt, scope};
|
use utoipa_actix_web::{AppExt, scope};
|
||||||
use utoipa_swagger_ui::{Config, SwaggerUi};
|
use utoipa_swagger_ui::{Config, SwaggerUi};
|
||||||
use crate::account::User;
|
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod airports;
|
mod airports;
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ use chrono::{DateTime, Utc};
|
|||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use reqwest::header::ETAG;
|
use reqwest::header::ETAG;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use sqlx::{Postgres, QueryBuilder};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::io::{Cursor, Read};
|
use std::io::{Cursor, Read};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
use sqlx::{Postgres, QueryBuilder};
|
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
static TIME_OFFSET: OnceLock<i64> = OnceLock::new();
|
static TIME_OFFSET: OnceLock<i64> = OnceLock::new();
|
||||||
@@ -310,9 +310,11 @@ impl MetarRow {
|
|||||||
let chunk_size = 1000;
|
let chunk_size = 1000;
|
||||||
|
|
||||||
for chunk in metars.chunks(chunk_size) {
|
for chunk in metars.chunks(chunk_size) {
|
||||||
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(
|
let mut query_builder: QueryBuilder<Postgres> = QueryBuilder::new(format!(
|
||||||
format!("INSERT INTO {} (icao, observation_time, raw_text, data) ", TABLE_NAME));
|
"INSERT INTO {} (icao, observation_time, raw_text, data) ",
|
||||||
query_builder.push_values(chunk, |mut b, metar | {
|
TABLE_NAME
|
||||||
|
));
|
||||||
|
query_builder.push_values(chunk, |mut b, metar| {
|
||||||
let row: Self = match metar.to_row() {
|
let row: Self = match metar.to_row() {
|
||||||
Ok(row) => row,
|
Ok(row) => row,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -395,11 +397,15 @@ impl Metar {
|
|||||||
Ok(datetime) => datetime.with_timezone(&Utc),
|
Ok(datetime) => datetime.with_timezone(&Utc),
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(err.into()),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
err.status,
|
err.status,
|
||||||
format!("Unexpected observation time field '{}': {}; {}", observation_time, metar_string, err)));
|
format!(
|
||||||
|
"Unexpected observation time field '{}': {}; {}",
|
||||||
|
observation_time, metar_string, err
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -570,7 +576,11 @@ impl Metar {
|
|||||||
metar.visibility_statute_mi = Some(format!("{:.2}", visibility));
|
metar.visibility_statute_mi = Some(format!("{:.2}", visibility));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Skipping unexpected visibility field '{}' ({})", metar_parts[0], metar_string);
|
log::warn!(
|
||||||
|
"Skipping unexpected visibility field '{}' ({})",
|
||||||
|
metar_parts[0],
|
||||||
|
metar_string
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::env;
|
|
||||||
use crate::http_client::HttpClient;
|
use crate::http_client::HttpClient;
|
||||||
use crate::metars::Metar;
|
use crate::metars::Metar;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use std::env;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::time::interval;
|
use tokio::time::interval;
|
||||||
|
|
||||||
|
|||||||
@@ -26,24 +26,24 @@ export function AirportSearch({ limit = 5 }: AirportSearchProps) {
|
|||||||
});
|
});
|
||||||
const nameResponse = await getAirports({
|
const nameResponse = await getAirports({
|
||||||
name: debounced,
|
name: debounced,
|
||||||
limit: limit - 1,
|
limit: limit - 1
|
||||||
});
|
});
|
||||||
let combined = [...icaoResponse.data, ...nameResponse.data];
|
let combined = [...icaoResponse.data, ...nameResponse.data];
|
||||||
combined = combined.slice(0, limit);
|
combined = combined.slice(0, limit);
|
||||||
return combined.map((airport) => ({
|
return combined.map((airport) => ({
|
||||||
key: airport.icao,
|
key: airport.icao,
|
||||||
value: airport.icao,
|
value: airport.icao,
|
||||||
label: `${airport.icao} - ${airport.name}`,
|
label: `${airport.icao} - ${airport.name}`
|
||||||
}));
|
}));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('airport search failed', err);
|
console.error('airport search failed', err);
|
||||||
return []
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fetch().then(d => {
|
fetch().then((d) => {
|
||||||
setData(d);
|
setData(d);
|
||||||
console.log(d)
|
console.log(d);
|
||||||
});
|
});
|
||||||
}, [debounced, limit]);
|
}, [debounced, limit]);
|
||||||
|
|
||||||
@@ -57,7 +57,6 @@ export function AirportSearch({ limit = 5 }: AirportSearchProps) {
|
|||||||
onOptionSubmit={() => {}}
|
onOptionSubmit={() => {}}
|
||||||
radius={'xl'}
|
radius={'xl'}
|
||||||
onBlur={() => setSearch('')}
|
onBlur={() => setSearch('')}
|
||||||
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -15,10 +15,7 @@ import { AirportSearch } from '@components/AirportSearch.tsx';
|
|||||||
// { link: '/metars', label: 'Metars' }
|
// { link: '/metars', label: 'Metars' }
|
||||||
// ];
|
// ];
|
||||||
|
|
||||||
const protectedPages = [
|
const protectedPages = ['/administration', '/profile'];
|
||||||
'/administration',
|
|
||||||
'/profile'
|
|
||||||
]
|
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const { user, setUser } = useUserContext();
|
const { user, setUser } = useUserContext();
|
||||||
@@ -73,11 +70,9 @@ export function Header() {
|
|||||||
setUser(undefined);
|
setUser(undefined);
|
||||||
|
|
||||||
// See if the current page is a protected page
|
// See if the current page is a protected page
|
||||||
const isProtected = protectedPages.some(pattern =>
|
const isProtected = protectedPages.some((pattern) => matchPath(pattern, pathname));
|
||||||
matchPath(pattern, pathname)
|
|
||||||
)
|
|
||||||
if (isProtected) {
|
if (isProtected) {
|
||||||
navigate('/', { replace: true })
|
navigate('/', { replace: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,20 +14,15 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
async function toggleFavorite(icao: string) {
|
async function toggleFavorite(icao: string) {
|
||||||
setFavorites((prev) => {
|
setFavorites((prev) => {
|
||||||
const isFav = prev.includes(icao)
|
const isFav = prev.includes(icao);
|
||||||
const next = isFav
|
const next = isFav ? prev.filter((i) => i !== icao) : [...prev, icao];
|
||||||
? prev.filter((i) => i !== icao)
|
|
||||||
: [...prev, icao]
|
|
||||||
|
|
||||||
;(isFav
|
(isFav ? removeFavorite(icao) : addFavorite(icao)).catch((err) => {
|
||||||
? removeFavorite(icao)
|
console.error('Sync failed, rolling back', err);
|
||||||
: addFavorite(icao)
|
setFavorites(prev);
|
||||||
).catch((err) => {
|
});
|
||||||
console.error('Sync failed, rolling back', err)
|
|
||||||
setFavorites(prev)
|
|
||||||
})
|
|
||||||
|
|
||||||
return next
|
return next;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,9 +52,9 @@ export function UserProvider({ children }: { children: ReactNode }) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user != undefined) {
|
if (user != undefined) {
|
||||||
getFavorites().then(f => {
|
getFavorites().then((f) => {
|
||||||
setFavorites(f)
|
setFavorites(f);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user