add data validation
This commit is contained in:
+32
-24
@@ -48,6 +48,9 @@ fn default_limit() -> u32 {
|
||||
|
||||
const MAX_LIMIT: u32 = 10000;
|
||||
|
||||
const LEAF_ONLY_FILTER: &str =
|
||||
"species_code != 'TOTAL' AND gear_code != 'TOTAL' AND zone_code != 'TOTAL'";
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Default)]
|
||||
pub struct SummaryQuery {
|
||||
pub species: Option<String>,
|
||||
@@ -75,6 +78,8 @@ impl SummaryQuery {
|
||||
let mut conditions: Vec<String> = Vec::new();
|
||||
let mut args: Vec<Box<dyn duckdb::ToSql>> = Vec::new();
|
||||
|
||||
conditions.push(format!("({LEAF_ONLY_FILTER})"));
|
||||
|
||||
if let Some(ref month) = self.month {
|
||||
conditions.push("month = ?".to_string());
|
||||
args.push(Box::new(month.clone()));
|
||||
@@ -177,6 +182,8 @@ impl AvailableFiltersQuery {
|
||||
let mut conditions: Vec<String> = Vec::new();
|
||||
let mut args: Vec<Box<dyn duckdb::ToSql>> = Vec::new();
|
||||
|
||||
conditions.push(format!("({LEAF_ONLY_FILTER})"));
|
||||
|
||||
if let Some(ref month_from) = self.month_from {
|
||||
conditions.push("month >= ?".to_string());
|
||||
args.push(Box::new(month_from.clone()));
|
||||
@@ -323,7 +330,7 @@ async fn get_species(State(state): State<AppState>) -> ApiResult<Json<Vec<Specie
|
||||
let conn = state.conn.clone();
|
||||
let rows = tokio::task::spawn_blocking(move || -> db::Result<Vec<SpeciesDto>> {
|
||||
let conn = conn.blocking_lock();
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM species ORDER BY code")?;
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM species WHERE code != 'TOTAL' ORDER BY code")?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(SpeciesDto {
|
||||
code: row.get(0)?,
|
||||
@@ -354,7 +361,8 @@ async fn get_landings(
|
||||
let mut sql = String::from(
|
||||
"SELECT month, species_code, species_label, gear_code, zone_code, \
|
||||
processing_code, preservation_code, shipsize_code, measure_code, value \
|
||||
FROM landings WHERE 1=1",
|
||||
FROM landings WHERE 1=1 \
|
||||
AND species_code != 'TOTAL' AND gear_code != 'TOTAL' AND zone_code != 'TOTAL'",
|
||||
);
|
||||
let mut args: Vec<Box<dyn duckdb::ToSql>> = Vec::new();
|
||||
let mut idx = 1;
|
||||
@@ -468,7 +476,7 @@ async fn get_summary(
|
||||
COALESCE(MAX(species_label), species_code) AS species_label, \
|
||||
SUM(CASE WHEN measure_code = 'VALUE' THEN value END) AS total_value, \
|
||||
SUM(CASE WHEN measure_code = 'MASS' THEN value END) AS total_mass \
|
||||
FROM landings WHERE 1=1{where_clause} AND species_code != 'TOTAL' \
|
||||
FROM landings WHERE 1=1{where_clause} \
|
||||
GROUP BY species_code, species_label \
|
||||
ORDER BY total_value DESC NULLS LAST \
|
||||
LIMIT 10"
|
||||
@@ -774,7 +782,7 @@ async fn get_zones(State(state): State<AppState>) -> ApiResult<Json<Vec<LookupDt
|
||||
let conn = state.conn.clone();
|
||||
let rows = tokio::task::spawn_blocking(move || -> db::Result<Vec<LookupDto>> {
|
||||
let conn = conn.blocking_lock();
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM zone ORDER BY code")?;
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM zone WHERE code != 'TOTAL' ORDER BY code")?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(LookupDto {
|
||||
code: row.get(0)?,
|
||||
@@ -796,7 +804,7 @@ async fn get_gear(State(state): State<AppState>) -> ApiResult<Json<Vec<LookupDto
|
||||
let conn = state.conn.clone();
|
||||
let rows = tokio::task::spawn_blocking(move || -> db::Result<Vec<LookupDto>> {
|
||||
let conn = conn.blocking_lock();
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM gear ORDER BY code")?;
|
||||
let mut stmt = conn.prepare("SELECT code, label FROM gear WHERE code != 'TOTAL' ORDER BY code")?;
|
||||
let rows = stmt.query_map([], |row| {
|
||||
Ok(LookupDto {
|
||||
code: row.get(0)?,
|
||||
@@ -831,8 +839,8 @@ mod tests {
|
||||
month: "2024M01".to_string(),
|
||||
species_code: "COD".to_string(),
|
||||
species_label: "Toskur".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -843,8 +851,8 @@ mod tests {
|
||||
month: "2024M01".to_string(),
|
||||
species_code: "COD".to_string(),
|
||||
species_label: "Toskur".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -855,8 +863,8 @@ mod tests {
|
||||
month: "2024M01".to_string(),
|
||||
species_code: "HER".to_string(),
|
||||
species_label: "Sild".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -867,8 +875,8 @@ mod tests {
|
||||
month: "2024M01".to_string(),
|
||||
species_code: "HER".to_string(),
|
||||
species_label: "Sild".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -879,8 +887,8 @@ mod tests {
|
||||
month: "2024M02".to_string(),
|
||||
species_code: "COD".to_string(),
|
||||
species_label: "Toskur".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -891,8 +899,8 @@ mod tests {
|
||||
month: "2024M02".to_string(),
|
||||
species_code: "COD".to_string(),
|
||||
species_label: "Toskur".to_string(),
|
||||
gear_code: "TOTAL".to_string(),
|
||||
zone_code: "TOTAL".to_string(),
|
||||
gear_code: "TR1".to_string(),
|
||||
zone_code: "FO".to_string(),
|
||||
processing_code: "TOTAL".to_string(),
|
||||
preservation_code: "TOTAL".to_string(),
|
||||
shipsize_code: "TOTAL".to_string(),
|
||||
@@ -1153,7 +1161,7 @@ mod tests {
|
||||
let state = test_state();
|
||||
let base = spawn_test_server(state).await;
|
||||
|
||||
let resp = reqwest::get(format!("{base}/api/summary?zone=TOTAL"))
|
||||
let resp = reqwest::get(format!("{base}/api/summary?zone=FO"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@@ -1167,7 +1175,7 @@ mod tests {
|
||||
let state = test_state();
|
||||
let base = spawn_test_server(state).await;
|
||||
|
||||
let resp = reqwest::get(format!("{base}/api/summary?gear=TOTAL"))
|
||||
let resp = reqwest::get(format!("{base}/api/summary?gear=TR1"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
@@ -1448,8 +1456,8 @@ mod tests {
|
||||
|
||||
let body: AvailableFiltersDto = resp.json().await.unwrap();
|
||||
assert!(body.species.contains(&"COD".to_string()));
|
||||
assert!(body.zones.contains(&"TOTAL".to_string()));
|
||||
assert!(body.gear.contains(&"TOTAL".to_string()));
|
||||
assert!(body.zones.contains(&"FO".to_string()));
|
||||
assert!(body.gear.contains(&"TR1".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1464,8 +1472,8 @@ mod tests {
|
||||
|
||||
let body: AvailableFiltersDto = resp.json().await.unwrap();
|
||||
assert!(body.species.contains(&"COD".to_string()));
|
||||
assert!(body.zones.contains(&"TOTAL".to_string()));
|
||||
assert!(body.gear.contains(&"TOTAL".to_string()));
|
||||
assert!(body.zones.contains(&"FO".to_string()));
|
||||
assert!(body.gear.contains(&"TR1".to_string()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1473,7 +1481,7 @@ mod tests {
|
||||
let state = test_state();
|
||||
let base = spawn_test_server(state).await;
|
||||
|
||||
let resp = reqwest::get(format!("{base}/api/available-filters?zone_multi=TOTAL"))
|
||||
let resp = reqwest::get(format!("{base}/api/available-filters?zone_multi=FO"))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
Reference in New Issue
Block a user