diff --git a/src/areas/models.rs b/src/areas/models.rs index e454994..6d4626f 100644 --- a/src/areas/models.rs +++ b/src/areas/models.rs @@ -15,7 +15,6 @@ use sea_orm::{query::*, DatabaseConnection}; use sea_query::Expr; use serde::Serialize; use serde_json::Value; -use tracing_subscriber::registry::Data; use utoipa::ToSchema; use uuid::Uuid; diff --git a/src/main.rs b/src/main.rs index 27ff427..9473aa4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -63,6 +63,7 @@ async fn main() { .nest("/v1/plot_samples", samples::views::router(db.clone())) .nest("/v1/sensors", sensors::views::router(db.clone())) .nest("/v1/transects", transects::views::router(db.clone())) + .nest("/v1/soil_types", soil::types::views::router(db.clone())) .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) .merge(Redoc::with_url("/redoc", ApiDoc::openapi())) .merge(Scalar::with_url("/scalar", ApiDoc::openapi())); diff --git a/src/samples/models.rs b/src/samples/models.rs index 80ccba6..d59dc72 100644 --- a/src/samples/models.rs +++ b/src/samples/models.rs @@ -144,85 +144,85 @@ impl PlotSample { plot_samples } - pub async fn get_one(id: Uuid, db: &DatabaseConnection) -> Option { - let sample = crate::samples::db::Entity::find() - .filter(crate::samples::db::Column::Id.eq(id)) - .one(db) - .await - .unwrap() - .unwrap(); + // pub async fn get_one(id: Uuid, db: &DatabaseConnection) -> Option { + // let sample = crate::samples::db::Entity::find() + // .filter(crate::samples::db::Column::Id.eq(id)) + // .one(db) + // .await + // .unwrap() + // .unwrap(); - let plot_obj = crate::plots::db::Entity::find() - .filter(crate::plots::db::Column::Id.eq(sample.plot_id)) - .one(db) - .await - .unwrap() - .unwrap(); + // let plot_obj = crate::plots::db::Entity::find() + // .filter(crate::plots::db::Column::Id.eq(sample.plot_id)) + // .one(db) + // .await + // .unwrap() + // .unwrap(); - let area = crate::areas::db::Entity::find() - .filter(crate::areas::db::Column::Id.eq(plot_obj.area_id)) - .one(db) - .await - .unwrap() - .unwrap(); + // let area = crate::areas::db::Entity::find() + // .filter(crate::areas::db::Column::Id.eq(plot_obj.area_id)) + // .one(db) + // .await + // .unwrap() + // .unwrap(); - let project = crate::projects::db::Entity::find() - .filter(crate::projects::db::Column::Id.eq(area.project_id)) - .one(db) - .await - .unwrap() - .unwrap(); + // let project = crate::projects::db::Entity::find() + // .filter(crate::projects::db::Column::Id.eq(area.project_id)) + // .one(db) + // .await + // .unwrap() + // .unwrap(); - let plot = crate::plots::models::PlotBasicWithAreaAndProject { - id: plot_obj.id, - name: plot_obj.name, - area: crate::areas::models::AreaBasicWithProject { - id: area.id, - name: area.name, - project: crate::common::models::GenericNameAndID { - id: project.id, - name: project.name, - }, - }, - }; + // let plot = crate::plots::models::PlotBasicWithAreaAndProject { + // id: plot_obj.id, + // name: plot_obj.name, + // area: crate::areas::models::AreaBasicWithProject { + // id: area.id, + // name: area.name, + // project: crate::common::models::GenericNameAndID { + // id: project.id, + // name: project.name, + // }, + // }, + // }; - Some(PlotSample { - id: sample.id, - name: sample.name, - upper_depth_cm: sample.upper_depth_cm, - lower_depth_cm: sample.lower_depth_cm, - plot_id: sample.plot_id, - sample_weight: sample.sample_weight, - subsample_weight: sample.subsample_weight, - ph: sample.ph, - rh: sample.rh, - loi: sample.loi, - mfc: sample.mfc, - c: sample.c, - n: sample.n, - cn: sample.cn, - clay_percent: sample.clay_percent, - silt_percent: sample.silt_percent, - sand_percent: sample.sand_percent, - fe_ug_per_g: sample.fe_ug_per_g, - na_ug_per_g: sample.na_ug_per_g, - al_ug_per_g: sample.al_ug_per_g, - k_ug_per_g: sample.k_ug_per_g, - ca_ug_per_g: sample.ca_ug_per_g, - mg_ug_per_g: sample.mg_ug_per_g, - mn_ug_per_g: sample.mn_ug_per_g, - s_ug_per_g: sample.s_ug_per_g, - cl_ug_per_g: sample.cl_ug_per_g, - p_ug_per_g: sample.p_ug_per_g, - si_ug_per_g: sample.si_ug_per_g, - subsample_replica_weight: sample.subsample_replica_weight, - fungi_per_g: sample.fungi_per_g, - bacteria_per_g: sample.bacteria_per_g, - archea_per_g: sample.archea_per_g, - methanogens_per_g: sample.methanogens_per_g, - methanotrophs_per_g: sample.methanotrophs_per_g, - replicate: sample.replicate, - plot: plot, - }) - } + // Some(PlotSample { + // id: sample.id, + // name: sample.name, + // upper_depth_cm: sample.upper_depth_cm, + // lower_depth_cm: sample.lower_depth_cm, + // plot_id: sample.plot_id, + // sample_weight: sample.sample_weight, + // subsample_weight: sample.subsample_weight, + // ph: sample.ph, + // rh: sample.rh, + // loi: sample.loi, + // mfc: sample.mfc, + // c: sample.c, + // n: sample.n, + // cn: sample.cn, + // clay_percent: sample.clay_percent, + // silt_percent: sample.silt_percent, + // sand_percent: sample.sand_percent, + // fe_ug_per_g: sample.fe_ug_per_g, + // na_ug_per_g: sample.na_ug_per_g, + // al_ug_per_g: sample.al_ug_per_g, + // k_ug_per_g: sample.k_ug_per_g, + // ca_ug_per_g: sample.ca_ug_per_g, + // mg_ug_per_g: sample.mg_ug_per_g, + // mn_ug_per_g: sample.mn_ug_per_g, + // s_ug_per_g: sample.s_ug_per_g, + // cl_ug_per_g: sample.cl_ug_per_g, + // p_ug_per_g: sample.p_ug_per_g, + // si_ug_per_g: sample.si_ug_per_g, + // subsample_replica_weight: sample.subsample_replica_weight, + // fungi_per_g: sample.fungi_per_g, + // bacteria_per_g: sample.bacteria_per_g, + // archea_per_g: sample.archea_per_g, + // methanogens_per_g: sample.methanogens_per_g, + // methanotrophs_per_g: sample.methanotrophs_per_g, + // replicate: sample.replicate, + // plot: plot, + // }) + // } } diff --git a/src/samples/views.rs b/src/samples/views.rs index 9b94374..93e42f3 100644 --- a/src/samples/views.rs +++ b/src/samples/views.rs @@ -7,7 +7,6 @@ use axum::{ http::header::HeaderMap, routing, Json, Router, }; -use sea_orm::sqlx::Result; use sea_orm::Condition; use sea_orm::EntityTrait; use sea_orm::{query::*, DatabaseConnection}; diff --git a/src/sensors/services.rs b/src/sensors/services.rs index 6da48b9..f8047c7 100644 --- a/src/sensors/services.rs +++ b/src/sensors/services.rs @@ -1,16 +1,16 @@ // use crate::common::models::ClosestFeature; -use crate::common::models::ClosestFeature; -use crate::sensors::data::db::Entity as SensorDataDB; -use crate::sensors::db::Entity as SensorDB; +// use crate::common::models::ClosestFeature; +// use crate::sensors::data::db::Entity as SensorDataDB; +// use crate::sensors::db::Entity as SensorDB; use lttb::lttb; -use sea_orm::DbBackend; -use sea_orm::{query::*, DatabaseConnection}; -use sea_query::Alias; -use sea_query::Expr; -use sea_query::Order; -use serde_json::json; -use std::cmp::min; -use uuid::Uuid; +// use sea_orm::DbBackend; +// use sea_orm::{query::*, DatabaseConnection}; +// use sea_query::Alias; +// use sea_query::Expr; +// use sea_query::Order; +// use serde_json::json; +// use std::cmp::min; +// use uuid::Uuid; // pub async fn fetch_closest_features( // sensor_id: Uuid, diff --git a/src/soil/types/db.rs b/src/soil/types/db.rs index b50597f..7ceba75 100644 --- a/src/soil/types/db.rs +++ b/src/soil/types/db.rs @@ -1,8 +1,9 @@ use chrono::NaiveDateTime; use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; use uuid::Uuid; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] #[sea_orm(table_name = "soiltype")] pub struct Model { pub name: String, diff --git a/src/soil/types/mod.rs b/src/soil/types/mod.rs index dec1023..eca28b0 100644 --- a/src/soil/types/mod.rs +++ b/src/soil/types/mod.rs @@ -1 +1,3 @@ pub mod db; +pub mod models; +pub mod views; diff --git a/src/soil/types/models.rs b/src/soil/types/models.rs new file mode 100644 index 0000000..cf0d92a --- /dev/null +++ b/src/soil/types/models.rs @@ -0,0 +1,20 @@ +use serde::Serialize; +use utoipa::ToSchema; +use uuid::Uuid; + +#[derive(ToSchema, Serialize)] +pub struct SoilType { + pub id: Uuid, + pub last_updated: chrono::NaiveDateTime, + pub name: Option, + pub description: String, + pub image: Option, +} + +#[derive(ToSchema, Serialize)] +pub struct SoilTypeBasic { + pub id: Uuid, + pub last_updated: chrono::NaiveDateTime, + pub name: Option, + pub description: String, +} diff --git a/src/soil/types/views.rs b/src/soil/types/views.rs new file mode 100644 index 0000000..5416e74 --- /dev/null +++ b/src/soil/types/views.rs @@ -0,0 +1,154 @@ +use crate::common::models::FilterOptions; +use axum::extract::Path; +use axum::http::StatusCode; +use axum::response::IntoResponse; +use axum::{ + extract::{Query, State}, + http::header::HeaderMap, + routing, Json, Router, +}; +use sea_orm::query::*; +use sea_orm::ColumnTrait; +use sea_orm::{Condition, DatabaseConnection, EntityTrait}; +use sea_query::{Alias, Expr, Order}; +use std::collections::HashMap; +use std::iter::Iterator; +use uuid::Uuid; + +pub fn router(db: DatabaseConnection) -> Router { + Router::new() + .route("/", routing::get(get_all)) + .route("/:id", routing::get(get_one)) + .with_state(db) +} + +#[utoipa::path(get, path = "/v1/soil_types", responses((status = 200, body = SoilType)))] +pub async fn get_all( + Query(params): Query, + State(db): State, +) -> impl IntoResponse { + // Default sorting and range values + let default_sort_column = "id"; + let default_sort_order = "ASC"; + + // Parse filter, range, and sort parameters + let filters: HashMap = if let Some(filter) = params.filter { + serde_json::from_str(&filter).unwrap_or_default() + } else { + HashMap::new() + }; + + let (offset, limit) = if let Some(range) = params.range { + let range_vec: Vec = serde_json::from_str(&range).unwrap_or(vec![0, 24]); + let start = range_vec.get(0).copied().unwrap_or(0); + let end = range_vec.get(1).copied().unwrap_or(24); + let limit = end - start + 1; + (start, limit) + } else { + (0, 25) + }; + + let (sort_column, sort_order) = if let Some(sort) = params.sort { + let sort_vec: Vec = serde_json::from_str(&sort).unwrap_or(vec![ + default_sort_column.to_string(), + default_sort_order.to_string(), + ]); + ( + sort_vec + .get(0) + .cloned() + .unwrap_or(default_sort_column.to_string()), + sort_vec + .get(1) + .cloned() + .unwrap_or(default_sort_order.to_string()), + ) + } else { + ( + default_sort_column.to_string(), + default_sort_order.to_string(), + ) + }; + + // Apply filters + let mut condition = Condition::all(); + for (key, mut value) in filters { + value = value.trim().to_string(); + + // Check if the value is a UUID, otherwise treat as a string filter + if let Ok(uuid) = Uuid::parse_str(&value) { + condition = condition.add(Expr::col(Alias::new(&key)).eq(uuid)); + } else { + condition = condition.add(Expr::col(Alias::new(&key)).eq(value)); + } + } + + // Sorting and pagination + let order_direction = if sort_order == "ASC" { + Order::Asc + } else { + Order::Desc + }; + + let order_column = match sort_column.as_str() { + "id" => super::db::Column::Id, + "name" => super::db::Column::Name, + "description" => super::db::Column::Description, + "last_updated" => super::db::Column::LastUpdated, + _ => super::db::Column::Id, // Default to sorting by ID + }; + + // Fetch transects with filtering, sorting, and pagination + let objs: Vec = super::db::Entity::find() + .filter(condition) + .order_by(order_column, order_direction) + .offset(offset) + .limit(limit) + .all(&db) + .await + .unwrap(); + + let transects: Vec = objs + .into_iter() + .map(|obj| super::models::SoilTypeBasic { + id: obj.id, + last_updated: obj.last_updated, + name: Some(obj.name), + description: obj.description, + }) + .collect(); + + // Get total count for content range header + let total_count: u64 = crate::transects::db::Entity::find() + .count(&db) + .await + .unwrap(); + let max_offset_limit = (offset + limit).min(total_count); + let resource_name = "soil_types"; + let content_range = format!( + "{} {}-{}/{}", + resource_name, + offset, + max_offset_limit - 1, + total_count + ); + + // Return Content-Range as a header + let mut headers = HeaderMap::new(); + headers.insert("Content-Range", content_range.parse().unwrap()); + (headers, Json(transects)) +} + +#[utoipa::path(get, path = "/v1/soil_types/{id}", responses((status = 200, body = Transect)))] +pub async fn get_one( + State(db): State, + Path(id): Path, +) -> impl IntoResponse { + let soil_type: Option = super::db::Entity::find() + .filter(super::db::Column::Id.eq(id)) + .one(&db) + .await + .unwrap(); + + (StatusCode::OK, Json(soil_type)) +} diff --git a/src/transects/views.rs b/src/transects/views.rs index 6b82a58..6a35cf4 100644 --- a/src/transects/views.rs +++ b/src/transects/views.rs @@ -11,8 +11,6 @@ use axum::{ use sea_orm::query::*; use sea_orm::{Condition, DatabaseConnection, EntityTrait}; use sea_query::{Alias, Expr, Order}; - -use serde_json::json; use std::collections::HashMap; use uuid::Uuid;