diff --git a/src/v1/mod.rs b/src/v1/mod.rs index b1f4cd3..cd12a16 100644 --- a/src/v1/mod.rs +++ b/src/v1/mod.rs @@ -1,11 +1,13 @@ mod builtin; mod debug; mod kcomebacks; +mod projects; mod updates; pub use builtin::get_builtin_routes as get_v1_builtin_routes; pub use debug::get_debug_routes as get_v1_debug_routes; pub use kcomebacks::get_kcomebacks_routes as get_v1_kcomebacks_routes; +pub use projects::get_project_routes as get_v1_project_routes; pub use updates::get_updates_routes as get_v1_updates_routes; use warp::Filter; @@ -14,5 +16,6 @@ pub fn get_v1_routes() -> impl warp::Filter impl warp::Filter + Clone { + warp::path("filter") + .and((warp::path("getall").and(warp::get()).and(warp::query::>()).and_then(filter_getall_handler)) + .or(warp::path("lastupdaterange").and(warp::get()).and(warp::query::>()).and_then(filter_lastupdaterange_handler)) + .or(warp::path("title").and(warp::get()).and(warp::query::>()).and_then(filter_title_handler)) + .or(warp::path("description").and(warp::get()).and(warp::query::>()).and_then(filter_description_handler)) + .or(warp::path("search").and(warp::get()).and(warp::query::>()).and_then(filter_search_handler)) + .or(warp::path("status").and(warp::get()).and(warp::query::>()).and_then(filter_status_handler)) + .or(warp::path("statuscolor").and(warp::get().and(warp::query::>()).and_then(filter_statuscolor_handler))) + .or(warp::path("category").and(warp::get().and(warp::query::>()).and_then(filter_category_handler))) + .or(warp::path("language").and(warp::get().and(warp::query::>()).and_then(filter_language_handler))) + .or(warp::path("getlangs").and(warp::get().and_then(filter_getlangs_handler))) + .or(warp::path("getstatuses").and(warp::get().and_then(filter_getstatuses_handler))) + .or(warp::path("getcolors").and(warp::get().and_then(filter_getcolors_handler))) + .or(warp::path("getcategories").and(warp::get().and_then(filter_getcategories_handler)))) +} + +async fn filter_getall_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 2 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .collect() + } + _ => Vec::new(), + }; + + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap() + 1).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + + +async fn filter_lastupdaterange_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let start = params.get("start").unwrap_or(&"".to_string()).to_string(); + let end = params.get("end").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // Parse the start and end dates (from unix time) to i64 integers + let start = start.parse::().unwrap_or(-1); + let end = end.parse::().unwrap_or(-1); + + // check if the parameters are valid + if params.len() > 4 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || start == -1 + || end == -1 + || end < start + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.last_update >= start && project.last_update <= end) + .collect() + } + _ => Vec::new(), + }; + + println!("{:?}", filtered_data); + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) + + +} + +async fn filter_title_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let title = params.get("title").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || title.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.title.to_lowercase().contains(title.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_description_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let description = params.get("description").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || description.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.description.to_lowercase().contains(description.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_search_handler(params: HashMap) -> Result { + // Access the parameters from the HashMap + let search = params.get("search").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() + || offset.is_empty() + || search.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.title.to_lowercase().contains(search.to_lowercase().as_str()) || project.description.to_lowercase().contains(search.to_lowercase().as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_status_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let status = params.get("status").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if status.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.status == status) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_statuscolor_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let statuscolor = params.get("statuscolor").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if statuscolor.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.statuscolor == statuscolor) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_category_handler(params: HashMap) -> Result { + // Access the parameres from the HashMap + let category = params.get("category").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if category.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .filter(|project| project.categories.iter().any(|cat| cat == category.as_str())) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_language_handler(params: HashMap) -> Result { + return Err(warp::reject::custom(NotImplementedError)); + return Ok(warp::reply::html("placeholder for compiler to be happy")); + // Access the parameres from the HashMap + let language = params.get("language").unwrap_or(&"".to_string()).to_string(); + let limit = params.get("limit").unwrap_or(&"".to_string()).to_string(); + let offset = params.get("offset").unwrap_or(&"".to_string()).to_string(); + + // check if the parameters are valid + if language.is_empty() + || params.len() > 3 + || !limit.parse::().is_ok() + || !offset.parse::().is_ok() + || limit.parse::().unwrap() < 0 + || limit.parse::().unwrap() > 50 + || offset.parse::().unwrap() < 0 + || limit.is_empty() || offset.is_empty() + { + return Err(warp::reject::custom(BadRequestError)); + } + + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + //.filter(|project| project.languages.iter().any(|lang| lang.contains_key(language))) + .collect() + } + _ => Vec::new(), + }; + + // get the total number of results + let total_results = filtered_data.len(); + + // apply the limit and offset + let filtered_data = filtered_data.iter().skip(offset.parse::().unwrap()).take(limit.parse::().unwrap()).collect::>(); + + // return the data + //Ok(warp::reply::json(&create_json_response(filtered_data, total_results))) +} + +async fn filter_getlangs_handler() -> Result { + return Err(warp::reject::custom(NotImplementedError)); + return Ok(warp::reply::html("placeholder for compiler to be happy")); + // fetch the data + let data = fetch_data().await.unwrap(); + + // filter the data + let filtered_data: Vec = match data { + Value::Array(items) => { + items + .iter() + .filter_map(|item| serde_json::from_value::(item.clone()).ok()) + .filter(|project| project.visible) + .collect() + } + _ => Vec::new(), + }; + + // filter the data + /*let all_language_keys: Vec<&String> = filtered_data + .iter() + .flat_map(|project| project.languages.iter().flat_map(|lang_map| lang_map.keys())) + .collect();*/ + + // get the total number of results + //let total_results = all_language_keys.len(); + + // return the data + //Ok(warp::reply::json(&create_json_response(all_language_keys, total_results))) +} + +async fn filter_getstatuses_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_statuses: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["status"].as_str().unwrap().to_string().is_empty() { + all_statuses.push(data[i]["status"].as_str().unwrap().to_string()); + } + } + + // remove duplicates + all_statuses.sort(); + all_statuses.dedup(); + + // get the total number of results + let total_results = all_statuses.len(); + + // json response + let json_response = json!({ + "results": all_statuses, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} + +async fn filter_getcolors_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_colors: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["statuscolor"].as_str().unwrap().to_string().is_empty() { + all_colors.push(data[i]["statuscolor"].as_str().unwrap().to_string()); + } + } + + // remove duplicates + all_colors.sort(); + all_colors.dedup(); + + // get the total number of results + let total_results = all_colors.len(); + + // json response + let json_response = json!({ + "results": all_colors, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} + +async fn filter_getcategories_handler() -> Result { + // fetch the data + let data = fetch_data().await.unwrap(); + + let mut all_categories: Vec = Vec::new(); + for i in 1..data.as_array().unwrap().len() { + if !data[i]["categories"].as_array().unwrap().is_empty() { + for j in 0..data[i]["categories"].as_array().unwrap().len() { + if !data[i]["categories"][j].as_str().unwrap().to_string().is_empty() { + all_categories.push(data[i]["categories"][j].as_str().unwrap().to_string()); + } + } + } + } + + // remove duplicates + all_categories.sort(); + all_categories.dedup(); + + // get the total number of results + let total_results = all_categories.len(); + + // json response + let json_response = json!({ + "results": all_categories, + "total_results": total_results, + }); + + Ok(warp::reply::json(&json_response)) +} diff --git a/src/v1/projects/mod.rs b/src/v1/projects/mod.rs index e306caf..d6f30ae 100644 --- a/src/v1/projects/mod.rs +++ b/src/v1/projects/mod.rs @@ -1,17 +1,26 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{Value, json}; +use warp::Filter; +use reqwest::Error; +use std::collections::HashMap; +mod filter; +use filter::get_project_filter_routes; + +use crate::error_responses::InternalServerError; pub fn get_project_routes() -> impl warp::Filter + Clone { warp::path("v1").and(warp::path("projects")) .and(warp::path("last_update").and(warp::get()).and_then(last_update) .or(warp::path("start_update").map(|| "Not implemented yet")) - .or(get_kcomebacks_upcoming_routes())) + .or(get_project_filter_routes())) } // get json data from https://https://cdn.jonasjones.dev/api/projects/projects.json pub async fn fetch_data() -> Result { - let url = "https://https://cdn.jonasjones.dev/api/projects/projects.json"; + let url = "https://cdn.jonasjones.dev/api/projects/projects.json"; let response = reqwest::get(url).await?; if response.status().is_success() { @@ -34,4 +43,41 @@ async fn last_update() -> Result { serde_json::Value::String(last_update) => Ok(warp::reply::json(&last_update)), _ => Err(warp::reject::custom(InternalServerError)), } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct Project { + pub title: String, + pub description: String, + pub status: String, + pub statuscolor: String, + pub categories: Vec, + pub languages: HashMap, + pub gh_api: String, + pub version: String, + pub backgroud: String, + pub links: HashMap, + pub visible: bool, + pub last_update: i64, +} + +pub fn create_json_response(items: Vec<&Project>, total_results: usize) -> Value { + // Serialize the vector of items to a JSON array + let results_array: Vec = items.into_iter().map(|item| json!(item)).collect(); + + // Build the final JSON object with "results" and "total_results" fields + let json_response = json!({ + "results": results_array, + "total_results": total_results, + }); + + json_response +} + +pub fn parse_item(item: &Value) -> Project { + // Parse the item into a struct + let item: Project = serde_json::from_value(item.clone()).unwrap(); + + // Return the parsed item + item } \ No newline at end of file