More app store stuff

This commit is contained in:
Will Galebach 2024-01-25 16:41:00 +00:00
parent 59b1f6a8dc
commit 251709f297
4 changed files with 242 additions and 200 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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(())
}

View File

@ -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::<kt::PackageMetadata>(&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,