Renamed service to api
This commit is contained in:
5
api/src/airports/mod.rs
Normal file
5
api/src/airports/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
mod model;
|
||||
mod routes;
|
||||
|
||||
pub use model::*;
|
||||
pub use routes::init_routes;
|
||||
432
api/src/airports/model.rs
Normal file
432
api/src/airports/model.rs
Normal file
@@ -0,0 +1,432 @@
|
||||
use std::fmt::Display;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::db;
|
||||
use crate::error::{ApiError, ApiResult};
|
||||
use crate::db::schema::airports;
|
||||
use diesel::prelude::*;
|
||||
use diesel::sql_query;
|
||||
use log::error;
|
||||
use postgis_diesel::types::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Runway {
|
||||
pub id: String,
|
||||
pub length_ft: f32,
|
||||
pub width_ft: f32,
|
||||
pub surface: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Frequency {
|
||||
pub id: String,
|
||||
pub frequency_mhz: f32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Airport {
|
||||
pub icao: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub iata: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub local: Option<String>,
|
||||
pub name: String,
|
||||
pub category: AirportCategory,
|
||||
pub iso_country: String,
|
||||
pub iso_region: String,
|
||||
pub municipality: String,
|
||||
pub elevation_ft: f32,
|
||||
pub latitude: f64,
|
||||
pub longitude: f64,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_tower: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub has_beacon: Option<bool>,
|
||||
pub runways: Vec<Runway>,
|
||||
pub frequencies: Vec<Frequency>,
|
||||
pub public: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct AirportMetarCache {
|
||||
pub icao: String,
|
||||
pub has_metar: bool,
|
||||
pub last_checked: chrono::NaiveDateTime,
|
||||
}
|
||||
|
||||
impl Into<QueryAirport> for Airport {
|
||||
fn into(self) -> QueryAirport {
|
||||
return QueryAirport {
|
||||
icao: self.icao.clone(),
|
||||
category: self.category.clone().to_string(),
|
||||
name: self.name.clone(),
|
||||
elevation_ft: self.elevation_ft,
|
||||
iso_country: self.iso_country.clone(),
|
||||
iso_region: self.iso_region.clone(),
|
||||
municipality: self.municipality.clone(),
|
||||
has_metar: false,
|
||||
point: Point::new(self.longitude, self.latitude, Some(4326)),
|
||||
data: match serde_json::to_value(&self) {
|
||||
Ok(d) => d,
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
serde_json::Value::Null
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl From<QueryAirport> for Airport {
|
||||
fn from(airport: QueryAirport) -> Self {
|
||||
serde_json::from_value(airport.data).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub enum AirportCategory {
|
||||
#[serde(rename = "small_airport")]
|
||||
Small,
|
||||
#[serde(rename = "medium_airport")]
|
||||
Medium,
|
||||
#[serde(rename = "large_airport")]
|
||||
Large,
|
||||
#[serde(rename = "heliport")]
|
||||
Heliport,
|
||||
#[serde(rename = "closed")]
|
||||
Closed,
|
||||
#[serde(rename = "seaplane_base")]
|
||||
Seaplane,
|
||||
#[serde(rename = "balloonport")]
|
||||
Balloonport,
|
||||
#[serde(rename = "unknown")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl FromStr for AirportCategory {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"small_airport" => Ok(AirportCategory::Small),
|
||||
"medium_airport" => Ok(AirportCategory::Medium),
|
||||
"large_airport" => Ok(AirportCategory::Large),
|
||||
"heliport" => Ok(AirportCategory::Heliport),
|
||||
"closed" => Ok(AirportCategory::Closed),
|
||||
"seaplane_base" => Ok(AirportCategory::Seaplane),
|
||||
"balloonport" => Ok(AirportCategory::Balloonport),
|
||||
_ => Ok(AirportCategory::Unknown),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AirportCategory {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
AirportCategory::Small => write!(f, "small_airport"),
|
||||
AirportCategory::Medium => write!(f, "medium_airport"),
|
||||
AirportCategory::Large => write!(f, "large_airport"),
|
||||
AirportCategory::Heliport => write!(f, "heliport"),
|
||||
AirportCategory::Closed => write!(f, "closed"),
|
||||
AirportCategory::Seaplane => write!(f, "seaplane_base"),
|
||||
AirportCategory::Balloonport => write!(f, "balloonport"),
|
||||
AirportCategory::Unknown => write!(f, "unknown"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, AsChangeset, Insertable, Queryable, QueryableByName)]
|
||||
#[diesel(table_name = airports)]
|
||||
pub struct QueryAirport {
|
||||
pub icao: String,
|
||||
pub category: String,
|
||||
pub name: String,
|
||||
pub elevation_ft: f32,
|
||||
pub iso_country: String,
|
||||
pub iso_region: String,
|
||||
pub municipality: String,
|
||||
pub has_metar: bool,
|
||||
pub point: Point,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueryFilters {
|
||||
pub icaos: Option<Vec<String>>,
|
||||
pub name: Option<String>,
|
||||
pub bounds: Option<Polygon<Point>>,
|
||||
pub categories: Option<Vec<AirportCategory>>,
|
||||
pub order_field: Option<QueryOrderField>,
|
||||
pub order_by: Option<QueryOrderBy>,
|
||||
pub has_metar: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for QueryFilters {
|
||||
fn default() -> Self {
|
||||
QueryFilters {
|
||||
icaos: None,
|
||||
name: None,
|
||||
bounds: None,
|
||||
categories: None,
|
||||
order_field: None,
|
||||
order_by: None,
|
||||
has_metar: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryOrderBy {
|
||||
Asc,
|
||||
Desc,
|
||||
}
|
||||
|
||||
impl FromStr for QueryOrderBy {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"asc" => Ok(QueryOrderBy::Asc),
|
||||
"desc" => Ok(QueryOrderBy::Desc),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum QueryOrderField {
|
||||
Icao,
|
||||
Name,
|
||||
Category,
|
||||
Country,
|
||||
Region,
|
||||
Municipality,
|
||||
}
|
||||
|
||||
impl FromStr for QueryOrderField {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"icao" => Ok(QueryOrderField::Icao),
|
||||
"name" => Ok(QueryOrderField::Name),
|
||||
"category" => Ok(QueryOrderField::Category),
|
||||
"iso_country" => Ok(QueryOrderField::Country),
|
||||
"iso_region" => Ok(QueryOrderField::Region),
|
||||
"municipality" => Ok(QueryOrderField::Municipality),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl QueryAirport {
|
||||
pub fn get_all(filters: &QueryFilters, limit: i32, page: i32) -> ApiResult<Vec<Self>> {
|
||||
let mut conn = db::connection()?;
|
||||
let mut query: String = "SELECT * FROM airports".to_string();
|
||||
query = format!("{} {}", query, QueryAirport::build_filter_query(&filters)?);
|
||||
|
||||
query = format!("{} ORDER BY has_metar DESC", query);
|
||||
if let Some(order_by) = &filters.order_by {
|
||||
match order_by {
|
||||
QueryOrderBy::Asc => {
|
||||
if let Some(order_field) = &filters.order_field {
|
||||
query = match order_field {
|
||||
QueryOrderField::Icao => format!("{}, icao ASC", query),
|
||||
QueryOrderField::Name => format!("{}, name ASC", query),
|
||||
QueryOrderField::Category => format!("{}, category ASC", query),
|
||||
QueryOrderField::Country => format!("{}, iso_country ASC", query),
|
||||
QueryOrderField::Region => format!("{}, iso_region ASC", query),
|
||||
QueryOrderField::Municipality => format!("{}, municipality ASC", query),
|
||||
};
|
||||
};
|
||||
}
|
||||
QueryOrderBy::Desc => {
|
||||
if let Some(order_field) = &filters.order_field {
|
||||
query = match order_field {
|
||||
QueryOrderField::Icao => format!("{}, icao DESC", query),
|
||||
QueryOrderField::Name => format!("{}, name DESC", query),
|
||||
QueryOrderField::Category => format!("{}, category DESC", query),
|
||||
QueryOrderField::Country => format!("{}, iso_country DESC", query),
|
||||
QueryOrderField::Region => format!("{}, iso_region DESC", query),
|
||||
QueryOrderField::Municipality => format!("{}, municipality DESC", query),
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
// Limit query to page and limit
|
||||
query = format!("{} LIMIT {} OFFSET {}", query, limit, (page - 1) * limit);
|
||||
|
||||
let airports: Vec<QueryAirport> = match sql_query(query).load(&mut conn) {
|
||||
Ok(a) => a,
|
||||
Err(err) => {
|
||||
return Err(ApiError {
|
||||
status: 500,
|
||||
message: format!("{}", err),
|
||||
})
|
||||
}
|
||||
};
|
||||
Ok(airports)
|
||||
}
|
||||
|
||||
pub fn get_count(filters: &QueryFilters) -> ApiResult<i64> {
|
||||
let mut conn = db::connection()?;
|
||||
let mut query = "SELECT COUNT(*) FROM airports".to_string();
|
||||
query = format!("{} {}", query, QueryAirport::build_filter_query(&filters)?);
|
||||
|
||||
// TODO: Fix this to use get_result() instead of building this table to do the load()
|
||||
diesel::table! {
|
||||
airports (count) {
|
||||
count -> BigInt,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Queryable, QueryableByName)]
|
||||
#[diesel(table_name = airports)]
|
||||
struct Count {
|
||||
count: i64,
|
||||
}
|
||||
|
||||
let count: Vec<Count> = match sql_query(query).load(&mut conn) {
|
||||
Ok(a) => a,
|
||||
Err(err) => {
|
||||
return Err(ApiError {
|
||||
status: 500,
|
||||
message: format!("{}", err),
|
||||
})
|
||||
}
|
||||
};
|
||||
return Ok(count[0].count);
|
||||
}
|
||||
|
||||
// TODO: Unsafe query, need to sanitize inputs
|
||||
fn build_filter_query(filters: &QueryFilters) -> ApiResult<String> {
|
||||
let mut query = "".to_string();
|
||||
let mut parts: Vec<String> = vec![];
|
||||
|
||||
if let Some(bounds) = &filters.bounds {
|
||||
// convert bounds to a WKT polygon
|
||||
if bounds.rings.len() > 1 {
|
||||
return Err(ApiError {
|
||||
status: 400,
|
||||
message: "Only one polygon is allowed".to_string(),
|
||||
});
|
||||
} else {
|
||||
let mut points: Vec<String> = vec![];
|
||||
bounds.rings.iter().for_each(|ring| {
|
||||
ring.iter().for_each(|point| {
|
||||
points.push(format!("{} {}", point.get_x(), point.get_y()));
|
||||
});
|
||||
});
|
||||
let bounds = format!("POLYGON(({}))", points.join(","));
|
||||
parts.push(format!(
|
||||
"ST_Contains(ST_GeomFromText('{}', 4326), point)",
|
||||
bounds
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(categories) = &filters.categories {
|
||||
parts.push(format!(
|
||||
"({})",
|
||||
categories
|
||||
.iter()
|
||||
.map(|category| format!("category = '{}'", category.to_string()))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" OR ")
|
||||
));
|
||||
}
|
||||
fn sanitize_icao(icao: &str) -> String {
|
||||
// Sanitize search to only allow [a-zA-Z0-9-\\s]
|
||||
icao
|
||||
.chars()
|
||||
.filter(|c| c.is_alphanumeric() || *c == '-' || *c == ' ')
|
||||
.collect::<String>()
|
||||
}
|
||||
if &filters.icaos.is_some() == &true && &filters.name.is_some() == &true {
|
||||
let icaos = filters.icaos.as_ref().unwrap();
|
||||
let name = sanitize_icao(filters.name.as_ref().unwrap());
|
||||
let icao_part = format!(
|
||||
"({})",
|
||||
icaos
|
||||
.iter()
|
||||
.map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao)))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" OR ")
|
||||
);
|
||||
let name_part = format!("name ILIKE '%{}%'", name);
|
||||
parts.push(format!("({} OR {})", icao_part, name_part));
|
||||
} else if let Some(icaos) = &filters.icaos {
|
||||
parts.push(format!(
|
||||
"({})",
|
||||
icaos
|
||||
.iter()
|
||||
.map(|icao| format!("icao ILIKE '{}'", sanitize_icao(icao)))
|
||||
.collect::<Vec<String>>()
|
||||
.join(" OR ")
|
||||
));
|
||||
} else if let Some(name) = &filters.name {
|
||||
let search = sanitize_icao(name);
|
||||
parts.push(format!("name ILIKE '%{}%'", search));
|
||||
}
|
||||
if let Some(has_metar) = &filters.has_metar {
|
||||
parts.push(format!("has_metar = {}", has_metar));
|
||||
}
|
||||
|
||||
if parts.len() > 0 {
|
||||
query = format!("{} WHERE {}", query, parts.join(" AND "));
|
||||
}
|
||||
|
||||
return Ok(query);
|
||||
}
|
||||
|
||||
pub fn get(icao: &str) -> ApiResult<Self> {
|
||||
let mut conn = db::connection()?;
|
||||
let airport = airports::table
|
||||
.filter(airports::icao.eq(icao))
|
||||
.first(&mut conn)?;
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn insert(airport: Self) -> ApiResult<Self> {
|
||||
let mut conn: r2d2::PooledConnection<diesel::r2d2::ConnectionManager<PgConnection>> =
|
||||
db::connection()?;
|
||||
let airport = Self::from(airport);
|
||||
let airport = diesel::insert_into(airports::table)
|
||||
.values(airport)
|
||||
.on_conflict_do_nothing()
|
||||
.get_result(&mut conn)?;
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn insert_all(airports: Vec<Self>) -> ApiResult<Vec<Self>> {
|
||||
let mut conn: r2d2::PooledConnection<diesel::r2d2::ConnectionManager<PgConnection>> =
|
||||
db::connection()?;
|
||||
let mut inserted_airports: Vec<Self> = vec![];
|
||||
for airport in airports {
|
||||
let airport = Self::from(airport);
|
||||
let airport = diesel::insert_into(airports::table)
|
||||
.values(airport)
|
||||
.on_conflict_do_nothing()
|
||||
.get_result(&mut conn)?;
|
||||
inserted_airports.push(airport);
|
||||
}
|
||||
Ok(inserted_airports)
|
||||
}
|
||||
|
||||
pub fn update(airport: Self) -> ApiResult<Self> {
|
||||
let mut conn = db::connection()?;
|
||||
let airport = diesel::update(airports::table)
|
||||
.filter(airports::icao.eq(airport.icao.clone()))
|
||||
.set(airport)
|
||||
.get_result(&mut conn)?;
|
||||
Ok(airport)
|
||||
}
|
||||
|
||||
pub fn delete(icao: Option<String>) -> ApiResult<usize> {
|
||||
let mut conn = db::connection()?;
|
||||
let res = match icao {
|
||||
Some(icao) => {
|
||||
diesel::delete(airports::table.filter(airports::icao.eq(icao))).execute(&mut conn)?
|
||||
}
|
||||
None => diesel::delete(airports::table).execute(&mut conn)?,
|
||||
};
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
303
api/src/airports/routes.rs
Normal file
303
api/src/airports/routes.rs
Normal file
@@ -0,0 +1,303 @@
|
||||
use std::str::FromStr;
|
||||
use futures_util::stream::StreamExt as _;
|
||||
|
||||
use crate::{
|
||||
airports::{QueryAirport, QueryFilters, QueryOrderField, QueryOrderBy, Airport, AirportCategory},
|
||||
db::{Response, Metadata},
|
||||
auth::{Auth, verify_role},
|
||||
};
|
||||
use actix_multipart::Multipart;
|
||||
use actix_web::{delete, get, post, put, web, HttpResponse, HttpRequest, ResponseError};
|
||||
use log::{error, warn};
|
||||
use postgis_diesel::types::{Polygon, Point};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct AirportsQuery {
|
||||
icaos: Option<String>,
|
||||
name: Option<String>,
|
||||
bounds: Option<String>,
|
||||
categories: Option<String>,
|
||||
order_field: Option<String>,
|
||||
order_by: Option<String>,
|
||||
has_metar: Option<String>,
|
||||
limit: Option<i32>,
|
||||
page: Option<i32>,
|
||||
}
|
||||
|
||||
#[post("/import")]
|
||||
async fn import_airports(mut payload: Multipart, auth: Auth) -> HttpResponse {
|
||||
if let Err(err) = verify_role(&auth, "admin") {
|
||||
return ResponseError::error_response(&err);
|
||||
};
|
||||
|
||||
while let Some(item) = payload.next().await {
|
||||
let mut bytes = web::BytesMut::new();
|
||||
let mut field = match item {
|
||||
Ok(field) => field,
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
|
||||
// Build bytes from chunks
|
||||
while let Some(chunk) = field.next().await {
|
||||
let data = match chunk {
|
||||
Ok(data) => data,
|
||||
Err(err) => {
|
||||
error!("Failed to get chunk: {}", err);
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
};
|
||||
bytes.extend_from_slice(&data);
|
||||
}
|
||||
|
||||
// Convert bytes to Vec<Airport>
|
||||
let airports: Vec<Airport> = match serde_json::from_slice(&bytes) {
|
||||
Ok(a) => a,
|
||||
Err(err) => {
|
||||
error!("Failed to parse JSON: {}", err);
|
||||
return ResponseError::error_response(&err);
|
||||
}
|
||||
};
|
||||
|
||||
// Convert Vec<Airport> to Vec<QueryAirport> and insert into database
|
||||
let query_airports: Vec<QueryAirport> = airports.into_iter().map(|a| a.into()).collect();
|
||||
match QueryAirport::insert_all(query_airports) {
|
||||
Ok(_) => {}
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
}
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[get("")]
|
||||
async fn get_airports(req: HttpRequest) -> HttpResponse {
|
||||
let params = web::Query::<AirportsQuery>::from_query(req.query_string()).unwrap();
|
||||
let mut filters = QueryFilters::default();
|
||||
filters.icaos = match ¶ms.icaos {
|
||||
Some(i) => Some(i.split(",").map(|s| s.to_string()).collect()),
|
||||
None => None,
|
||||
};
|
||||
filters.name = params.name.clone();
|
||||
filters.categories = match ¶ms.categories {
|
||||
Some(c) => Some(
|
||||
c.split(",")
|
||||
.map(|s| AirportCategory::from_str(s).unwrap())
|
||||
.collect(),
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
filters.bounds = match ¶ms.bounds {
|
||||
Some(b) => {
|
||||
let bounds: Vec<&str> = b.split(",").collect();
|
||||
if bounds.len() != 4 {
|
||||
warn!("Expected 4 bounds, received {}: {}", bounds.len(), b);
|
||||
return HttpResponse::UnprocessableEntity().body(format!(
|
||||
"Received {}; expected NE_LAT,NE_LON,SW_LAT,SW_LON",
|
||||
b
|
||||
));
|
||||
}
|
||||
let ne_lat = match bounds[0].parse::<f64>() {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
return HttpResponse::UnprocessableEntity().body(format!("{}", err));
|
||||
}
|
||||
};
|
||||
let ne_lon = match bounds[1].parse::<f64>() {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
return HttpResponse::UnprocessableEntity().body(format!("{}", err));
|
||||
}
|
||||
};
|
||||
let sw_lat = match bounds[2].parse::<f64>() {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
return HttpResponse::UnprocessableEntity().body(format!("{}", err));
|
||||
}
|
||||
};
|
||||
let sw_lon = match bounds[3].parse::<f64>() {
|
||||
Ok(b) => b,
|
||||
Err(err) => {
|
||||
warn!("{}", err);
|
||||
return HttpResponse::UnprocessableEntity().body(format!("{}", err));
|
||||
}
|
||||
};
|
||||
let mut polygon: Polygon<Point> = Polygon::new(Some(4326));
|
||||
polygon.add_point(Point {
|
||||
x: sw_lon,
|
||||
y: sw_lat,
|
||||
srid: Some(4326),
|
||||
});
|
||||
polygon.add_point(Point {
|
||||
x: ne_lon,
|
||||
y: sw_lat,
|
||||
srid: Some(4326),
|
||||
});
|
||||
polygon.add_point(Point {
|
||||
x: ne_lon,
|
||||
y: ne_lat,
|
||||
srid: Some(4326),
|
||||
});
|
||||
polygon.add_point(Point {
|
||||
x: sw_lon,
|
||||
y: ne_lat,
|
||||
srid: Some(4326),
|
||||
});
|
||||
polygon.add_point(Point {
|
||||
x: sw_lon,
|
||||
y: sw_lat,
|
||||
srid: Some(4326),
|
||||
});
|
||||
Some(polygon)
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
filters.order_by = match ¶ms.order_by {
|
||||
Some(o) => Some(QueryOrderBy::from_str(&o).unwrap()),
|
||||
None => None,
|
||||
};
|
||||
filters.order_field = match ¶ms.order_field {
|
||||
Some(o) => Some(QueryOrderField::from_str(&o).unwrap()),
|
||||
None => None,
|
||||
};
|
||||
filters.has_metar = match ¶ms.has_metar {
|
||||
Some(h) => Some(h.parse::<bool>().unwrap()),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let limit = match params.limit {
|
||||
Some(l) => l,
|
||||
None => 100,
|
||||
};
|
||||
let page = match params.page {
|
||||
Some(p) => p,
|
||||
None => 1,
|
||||
};
|
||||
let total = match QueryAirport::get_count(&filters) {
|
||||
Ok(t) => t,
|
||||
Err(_) => 0,
|
||||
};
|
||||
|
||||
match web::block(move || QueryAirport::get_all(&filters, limit, page))
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
Ok(a) => {
|
||||
// Convert Vec<QueryAirport> to Vec<Airport>
|
||||
let mut airports: Vec<Airport> = vec![];
|
||||
for airport in a {
|
||||
airports.push(airport.into());
|
||||
}
|
||||
HttpResponse::Ok().json(Response {
|
||||
data: airports,
|
||||
meta: Some(Metadata { page, limit, total }),
|
||||
})
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/{icao}")]
|
||||
async fn get_airport(icao: web::Path<String>) -> HttpResponse {
|
||||
match QueryAirport::get(&icao.into_inner()) {
|
||||
Ok(a) => {
|
||||
let airport: Airport = a.into();
|
||||
HttpResponse::Ok().json(airport)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[post("")]
|
||||
async fn create_airport(airport: web::Json<Airport>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {}
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
let query_airport: QueryAirport = airport.into_inner().into();
|
||||
match QueryAirport::insert(query_airport) {
|
||||
Ok(a) => {
|
||||
let airport: Airport = a.into();
|
||||
HttpResponse::Ok().json(airport)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[put("/{icao}")]
|
||||
async fn update_airport(
|
||||
_icao: web::Path<String>,
|
||||
airport: web::Json<Airport>,
|
||||
auth: Auth,
|
||||
) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {}
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
let query_airport: QueryAirport = airport.into_inner().into();
|
||||
match QueryAirport::update(query_airport) {
|
||||
Ok(a) => {
|
||||
let airport: Airport = a.into();
|
||||
HttpResponse::Ok().json(airport)
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("")]
|
||||
async fn delete_airports(auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {}
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
match QueryAirport::delete(None) {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("/{icao}")]
|
||||
async fn delete_airport(icao: web::Path<String>, auth: Auth) -> HttpResponse {
|
||||
let _ = match verify_role(&auth, "admin") {
|
||||
Ok(_) => {}
|
||||
Err(err) => return ResponseError::error_response(&err),
|
||||
};
|
||||
match QueryAirport::delete(Some(icao.into_inner())) {
|
||||
Ok(_) => HttpResponse::NoContent().finish(),
|
||||
Err(err) => {
|
||||
error!("{}", err);
|
||||
err.to_http_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_routes(config: &mut web::ServiceConfig) {
|
||||
config.service(
|
||||
web::scope("airports")
|
||||
.service(import_airports)
|
||||
.service(get_airports)
|
||||
.service(get_airport)
|
||||
.service(create_airport)
|
||||
.service(update_airport)
|
||||
.service(delete_airports)
|
||||
.service(delete_airport),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user