use std::{collections::HashMap, sync::Arc}; use axum::{extract::Path, http, response::{Html, IntoResponse}, routing::get, Json, Router}; use blake2::{Blake2b512, Digest}; use rand::Rng; use offline_web_model::Reference; #[derive(Debug)] pub struct AddressableObject { pub address: String, pub content: String, } fn random_object() -> AddressableObject { let mut rng = rand::rng(); let random_size = rng.random_range(50..=4096); let random_string: String = (0..random_size) .map(|_| rng.sample(rand::distr::Alphanumeric) as char) .collect(); let mut hasher = Blake2b512::new(); hasher.update(&random_string); let hash = format!("{:x}", hasher.finalize()); AddressableObject { address: hash, content: random_string, } } fn random_references_and_objects() -> (Arc, Arc>>, Arc>) { let path_root = String::from("ref/0"); let mut objects = HashMap::new(); let mut refs = HashMap::new(); let mut root_ref = Reference::new( "username:0".to_string(), String::from("0"), path_root.clone(), ); let mut root_hasher = Blake2b512::new(); for i in 1..=10 { let mut item_ref = Reference::new( format!("item:{}", i), format!("0:{}", i), format!("/item/{}", i), ); let mut hasher = Blake2b512::new(); for j in 1..=10 { let object = random_object(); hasher.update(&object.content); let leaf_ref = Reference::new( format!("item:{}:subitem:{}", i, j), format!("{}", object.address), format!("/item/{}/subitem/{}", i, j), ).to_arc(); item_ref = item_ref.add_dep(leaf_ref.clone()); objects.insert(object.address.clone(), object); hasher.update(&leaf_ref.content_address); refs.insert(leaf_ref.path.clone(), leaf_ref); } let hash = format!("{:x}", hasher.finalize()); item_ref.content_address = hash; root_hasher.update(&item_ref.content_address); let rc_ref = item_ref.to_arc(); root_ref = root_ref.add_dep(rc_ref.clone()); refs.insert(rc_ref.path.clone(), rc_ref); } root_ref.content_address = format!("{:x}", root_hasher.finalize()); let rc_root = root_ref.to_arc(); refs.insert(rc_root.path.clone(), rc_root.clone()); dbg!(&objects); (rc_root, Arc::new(refs), Arc::new(objects)) } // TODO(jeremy): Allow this to autoexpand the content_addresses? async fn all_references(root_ref: Arc) -> Json> { Json(root_ref) } async fn ref_path(refs: Arc>>, Path(path): Path) -> Json> { let path = format!("/item/{}", path); match refs.get(&path) { Some(r) => Json(r.clone()), None => todo!("Return a 404?"), } } async fn object_path(objects: Arc>, Path(addr): Path) -> String { dbg!(&addr); match objects.get(&addr) { Some(o) => o.content.clone(), None => todo!("Return a 404?"), } } async fn get_client_js() -> impl IntoResponse { ( [(http::header::CONTENT_TYPE, "application/javascript")], include_str!("../static/client.js"), ) } pub fn endpoints(root_ref: Arc, refs: Arc>>, objects: Arc>) -> Router { Router::new().nest( "/api/v1", Router::new().nest( "/ref", Router::new() .route("/all/username", get({ let state = root_ref.clone(); move || all_references(state) })) .route("/item/{*path}", get({ let refs = refs.clone(); move |path| ref_path(refs, path) })) ).nest( "/object", Router::new() .route("/{addr}", get({ let objects = objects.clone(); move |addr| object_path(objects, addr) })) ), ) .route("/lib/client.js", get(get_client_js)) .route("/ui/", get(|| async { Html(include_str!("../static/index.html")).into_response() })) } // TODO(jwall): Javascript test script pub async fn serve() { // run our app with hyper, listening globally on port 3000 let (root_ref, refs, objects) = random_references_and_objects(); let listener = tokio::net::TcpListener::bind("127.0.0.1:3000") .await .unwrap(); println!("Server ui starting on http://127.0.0.1:3000/ui/"); axum::serve(listener, endpoints(root_ref, refs, objects)).await.unwrap(); }