From 251709f29725ea1ec4929c5ad6b0245371f79f27 Mon Sep 17 00:00:00 2001 From: Will Galebach Date: Thu, 25 Jan 2024 16:41:00 +0000 Subject: [PATCH] More app store stuff --- modules/app_store/app_store/Cargo.lock | 7 + modules/app_store/app_store/Cargo.toml | 1 + modules/app_store/app_store/src/http_api.rs | 229 ++++++++++++++++++++ modules/app_store/app_store/src/lib.rs | 205 +----------------- 4 files changed, 242 insertions(+), 200 deletions(-) create mode 100644 modules/app_store/app_store/src/http_api.rs diff --git a/modules/app_store/app_store/Cargo.lock b/modules/app_store/app_store/Cargo.lock index 15eb9dab..5d2ef3ee 100644 --- a/modules/app_store/app_store/Cargo.lock +++ b/modules/app_store/app_store/Cargo.lock @@ -19,6 +19,7 @@ dependencies = [ "serde", "serde_json", "sha2", + "urlencoding", "wit-bindgen", ] @@ -473,6 +474,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "version_check" version = "0.9.4" diff --git a/modules/app_store/app_store/Cargo.toml b/modules/app_store/app_store/Cargo.toml index 7f0117c6..145255e9 100644 --- a/modules/app_store/app_store/Cargo.toml +++ b/modules/app_store/app_store/Cargo.toml @@ -13,6 +13,7 @@ anyhow = "1.0" bincode = "1.3.3" kinode_process_lib = { git = "https://github.com/uqbar-dao/process_lib.git", tag = "v0.5.5-alpha" } rand = "0.8" +urlencoding = "2.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sha2 = "0.10.8" diff --git a/modules/app_store/app_store/src/http_api.rs b/modules/app_store/app_store/src/http_api.rs new file mode 100644 index 00000000..df761886 --- /dev/null +++ b/modules/app_store/app_store/src/http_api.rs @@ -0,0 +1,229 @@ +use std::collections::HashMap; + +use kinode_process_lib::{http::{send_response, IncomingHttpRequest, StatusCode}, Address}; + +use crate::{PackageListing, State}; + +pub fn handle_http_request( + our: &Address, + state: &mut State, + req: IncomingHttpRequest, +) -> anyhow::Result<()> { + let path = req.path()?; + let method = req.method()?; + + let (status_code, headers, body) = match path.as_str() { + "/apps" => { + match method.as_str() { + "GET" => { + // TODO: Return a list of the user's apps + ( + StatusCode::OK, + None, + serde_json::to_vec(&vec![ + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Chess".to_string(), + icon: "".to_string(), + package_name: "chess".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "File Transfer".to_string(), + icon: "".to_string(), + package_name: "file_transfer".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + ])?, + ) + } + "POST" => { + // Add an app + (StatusCode::CREATED, None, format!("Installed").into_bytes()) + } + _ => ( + StatusCode::METHOD_NOT_ALLOWED, + None, + format!("Invalid method {} for {}", method, path).into_bytes(), + ), + } + } + "/apps/:id" => { + let Some(app_id) = path.split("/").last() else { + return Err(anyhow::anyhow!("No app ID")); + }; + + match method.as_str() { + "PUT" => { + // Update an app + ( + StatusCode::NO_CONTENT, + None, + format!("Updated").into_bytes(), + ) + } + "DELETE" => { + // Uninstall an app + ( + StatusCode::NO_CONTENT, + None, + format!("Uninstalled").into_bytes(), + ) + } + _ => ( + StatusCode::METHOD_NOT_ALLOWED, + None, + format!("Invalid method {} for {}", method, path).into_bytes(), + ), + } + } + "/apps/latest" => { + match method.as_str() { + "GET" => { + // Return a list of latest apps + // The first 2 will show up in "featured" + ( + StatusCode::OK, + None, + serde_json::to_vec(&vec![ + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Remote".to_string(), + icon: "".to_string(), + package_name: "remote".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Happy Path".to_string(), + icon: "".to_string(), + package_name: "happy_path".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Meme Deck".to_string(), + icon: "".to_string(), + package_name: "meme_deck".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Sheep Simulator".to_string(), + icon: "".to_string(), + package_name: "sheep_simulator".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + ])?, + ) + } + _ => ( + StatusCode::METHOD_NOT_ALLOWED, + None, + format!("Invalid method {} for {}", method, path).into_bytes(), + ), + } + } + "/apps/search/:query" => { + match method.as_str() { + "GET" => { + let Some(encoded_query) = path.split("/").last() else { + return Err(anyhow::anyhow!("No query")); + }; + let query = urlencoding::decode(encoded_query).expect("UTF-8"); + + // Return a list of apps matching the query + // Query by name, publisher, package_name, description, website + ( + StatusCode::OK, + None, + serde_json::to_vec(&vec![ + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Winch".to_string(), + icon: "".to_string(), + package_name: "winch".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + PackageListing { + owner: our.node.clone(), + publisher: our.node.clone(), + name: "Bucket".to_string(), + icon: "".to_string(), + package_name: "bucket".to_string(), + description: Some("A test app".to_string()), + website: Some("https://example.com".to_string()), + rating: 3.0, + versions: HashMap::new(), + mirrors: vec![], + }, + ])?, + ) + } + _ => ( + StatusCode::METHOD_NOT_ALLOWED, + None, + format!("Invalid method {} for {}", method, path).into_bytes(), + ), + } + } + "/apps/publish" => { + match method.as_str() { + "POST" => { + // Publish an app + (StatusCode::OK, None, format!("Success").into_bytes()) + } + _ => ( + StatusCode::METHOD_NOT_ALLOWED, + None, + format!("Invalid method {} for {}", method, path).into_bytes(), + ), + } + } + _ => ( + StatusCode::NOT_FOUND, + None, + format!("Path not found: {}", path).into_bytes(), + ), + }; + + send_response(status_code, headers, body)?; + + Ok(()) +} diff --git a/modules/app_store/app_store/src/lib.rs b/modules/app_store/app_store/src/lib.rs index d5d588b7..8284cf28 100644 --- a/modules/app_store/app_store/src/lib.rs +++ b/modules/app_store/app_store/src/lib.rs @@ -1,5 +1,5 @@ use kinode_process_lib::http::{ - bind_http_path, send_response, serve_ui, HttpServerRequest, IncomingHttpRequest, StatusCode, + bind_http_path, serve_ui, HttpServerRequest }; use kinode_process_lib::kernel_types as kt; use kinode_process_lib::*; @@ -21,6 +21,8 @@ mod ft_worker_lib; use ft_worker_lib::{ spawn_receive_transfer, spawn_transfer, FTWorkerCommand, FTWorkerResult, FileTransferContext, }; +mod http_api; +use http_api::handle_http_request; /// App Store: /// acts as both a local package manager and a protocol to share packages across the network. @@ -214,203 +216,6 @@ fn init(our: Address) { } } -fn handle_http_request( - our: &Address, - state: &mut State, - req: IncomingHttpRequest, -) -> anyhow::Result<()> { - let path = req.path()?; - let method = req.method()?; - - let (status_code, headers, body) = match path.as_str() { - "/apps" => { - match method.as_str() { - "GET" => { - // TODO: Return a list of the user's apps - ( - StatusCode::OK, - None, - serde_json::to_vec(&vec![ - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "Chess".to_string(), - icon: "".to_string(), - package_name: "chess".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "File Transfer".to_string(), - icon: "".to_string(), - package_name: "file_transfer".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - ])?, - ) - } - "POST" => { - // Add an app - (StatusCode::CREATED, None, format!("Installed").into_bytes()) - } - _ => ( - StatusCode::METHOD_NOT_ALLOWED, - None, - format!("Invalid method {} for {}", method, path).into_bytes(), - ), - } - } - "/apps/:id" => { - let Some(app_id) = path.split("/").last() else { - return Err(anyhow::anyhow!("No app ID")); - }; - - match method.as_str() { - "PUT" => { - // Update an app - ( - StatusCode::NO_CONTENT, - None, - format!("Updated").into_bytes(), - ) - } - "DELETE" => { - // Uninstall an app - ( - StatusCode::NO_CONTENT, - None, - format!("Uninstalled").into_bytes(), - ) - } - _ => ( - StatusCode::METHOD_NOT_ALLOWED, - None, - format!("Invalid method {} for {}", method, path).into_bytes(), - ), - } - } - "/apps/latest" => { - match method.as_str() { - "GET" => { - // Return a list of latest apps - ( - StatusCode::OK, - None, - serde_json::to_vec(&vec![ - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "Remote".to_string(), - icon: "".to_string(), - package_name: "remote".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "Happy Path".to_string(), - icon: "".to_string(), - package_name: "happy_path".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - ])?, - ) - } - _ => ( - StatusCode::METHOD_NOT_ALLOWED, - None, - format!("Invalid method {} for {}", method, path).into_bytes(), - ), - } - } - "/apps/search/:query" => { - match method.as_str() { - "GET" => { - let Some(query) = path.split("/").last() else { - return Err(anyhow::anyhow!("No query")); - }; - // Return a list of apps matching the query - // Query by name, publisher, package_name, description, website - ( - StatusCode::OK, - None, - serde_json::to_vec(&vec![ - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "Winch".to_string(), - icon: "".to_string(), - package_name: "winch".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - PackageListing { - owner: our.node.clone(), - publisher: our.node.clone(), - name: "Bucket".to_string(), - icon: "".to_string(), - package_name: "bucket".to_string(), - description: Some("A test app".to_string()), - website: Some("https://example.com".to_string()), - rating: 3.0, - versions: HashMap::new(), - mirrors: vec![], - }, - ])?, - ) - } - _ => ( - StatusCode::METHOD_NOT_ALLOWED, - None, - format!("Invalid method {} for {}", method, path).into_bytes(), - ), - } - } - "/apps/publish" => { - match method.as_str() { - "POST" => { - // Publish an app - (StatusCode::OK, None, format!("Success").into_bytes()) - } - _ => ( - StatusCode::METHOD_NOT_ALLOWED, - None, - format!("Invalid method {} for {}", method, path).into_bytes(), - ), - } - } - _ => ( - StatusCode::NOT_FOUND, - None, - format!("Path not found: {}", path).into_bytes(), - ), - }; - - send_response(status_code, headers, body)?; - - Ok(()) -} - fn handle_message(our: &Address, mut state: &mut State, message: &Message) -> anyhow::Result<()> { match message { Message::Request { @@ -631,13 +436,13 @@ fn handle_new_package( let metadata = String::from_utf8(blob.bytes)?; let metadata = serde_json::from_str::(&metadata)?; - let versions = HashMap::new(); + let mut versions = HashMap::new(); versions.insert(metadata.version, version_hash); let listing_data = PackageListing { owner: our.node.clone(), publisher: our.node.clone(), - name: metadata.package, + name: metadata.package.clone(), icon: "".to_string(), package_name: metadata.package, description: metadata.description,