2025-04-02 18:23:03 -04:00
|
|
|
use std::{collections::HashMap, sync::Arc};
|
2025-03-29 20:32:25 -04:00
|
|
|
|
2025-04-01 21:34:58 -04:00
|
|
|
use axum::{extract::Path, http, response::{Html, IntoResponse}, routing::get, Json, Router};
|
2025-04-02 18:23:03 -04:00
|
|
|
use blake2::{Blake2b512, Digest};
|
|
|
|
use rand::Rng;
|
2025-03-28 17:46:09 -04:00
|
|
|
|
2025-04-12 15:53:40 -04:00
|
|
|
use offline_web_model::Reference;
|
2025-03-28 17:46:09 -04:00
|
|
|
|
2025-04-02 18:23:03 -04:00
|
|
|
#[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<Reference>, Arc<HashMap<String, Arc<Reference>>>, Arc<HashMap<String, AddressableObject>>) {
|
2025-03-30 15:02:46 -04:00
|
|
|
let path_root = String::from("ref/0");
|
2025-04-02 18:23:03 -04:00
|
|
|
let mut objects = HashMap::new();
|
|
|
|
let mut refs = HashMap::new();
|
2025-03-30 15:02:46 -04:00
|
|
|
let mut root_ref = Reference::new(
|
|
|
|
"username:0".to_string(),
|
|
|
|
String::from("0"),
|
|
|
|
path_root.clone(),
|
|
|
|
);
|
2025-04-02 18:23:03 -04:00
|
|
|
let mut root_hasher = Blake2b512::new();
|
2025-03-30 15:02:46 -04:00
|
|
|
for i in 1..=10 {
|
|
|
|
let mut item_ref = Reference::new(
|
|
|
|
format!("item:{}", i),
|
|
|
|
format!("0:{}", i),
|
2025-04-02 18:23:03 -04:00
|
|
|
format!("/item/{}", i),
|
2025-03-30 15:02:46 -04:00
|
|
|
);
|
2025-04-02 18:23:03 -04:00
|
|
|
let mut hasher = Blake2b512::new();
|
2025-03-30 15:02:46 -04:00
|
|
|
for j in 1..=10 {
|
2025-04-02 18:23:03 -04:00
|
|
|
let object = random_object();
|
|
|
|
hasher.update(&object.content);
|
|
|
|
let leaf_ref = Reference::new(
|
2025-03-30 15:02:46 -04:00
|
|
|
format!("item:{}:subitem:{}", i, j),
|
2025-04-02 18:23:03 -04:00
|
|
|
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);
|
2025-03-30 15:02:46 -04:00
|
|
|
}
|
2025-04-02 18:23:03 -04:00
|
|
|
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);
|
2025-03-30 15:02:46 -04:00
|
|
|
}
|
2025-04-02 18:23:03 -04:00
|
|
|
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))
|
2025-03-30 15:02:46 -04:00
|
|
|
}
|
|
|
|
|
2025-04-12 15:53:40 -04:00
|
|
|
// TODO(jeremy): Allow this to autoexpand the content_addresses?
|
2025-04-02 18:23:03 -04:00
|
|
|
async fn all_references(root_ref: Arc<Reference>) -> Json<Arc<Reference>> {
|
|
|
|
Json(root_ref)
|
2025-03-30 15:02:46 -04:00
|
|
|
}
|
|
|
|
|
2025-04-02 18:23:03 -04:00
|
|
|
async fn ref_path(refs: Arc<HashMap<String, Arc<Reference>>>, Path(path): Path<String>) -> Json<Arc<Reference>> {
|
|
|
|
let path = format!("/item/{}", path);
|
|
|
|
match refs.get(&path) {
|
|
|
|
Some(r) => Json(r.clone()),
|
|
|
|
None => todo!("Return a 404?"),
|
|
|
|
}
|
2025-03-30 15:02:46 -04:00
|
|
|
}
|
|
|
|
|
2025-04-15 21:58:57 -04:00
|
|
|
async fn object_path(objects: Arc<HashMap<String, AddressableObject>>, Path(addr): Path<String>) -> String {
|
|
|
|
dbg!(&addr);
|
|
|
|
match objects.get(&addr) {
|
2025-04-02 18:23:03 -04:00
|
|
|
Some(o) => o.content.clone(),
|
|
|
|
None => todo!("Return a 404?"),
|
|
|
|
}
|
2025-03-29 20:32:25 -04:00
|
|
|
}
|
|
|
|
|
2025-03-30 21:18:08 -04:00
|
|
|
async fn get_client_js() -> impl IntoResponse {
|
|
|
|
(
|
|
|
|
[(http::header::CONTENT_TYPE, "application/javascript")],
|
|
|
|
include_str!("../static/client.js"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2025-04-02 18:23:03 -04:00
|
|
|
pub fn endpoints(root_ref: Arc<Reference>, refs: Arc<HashMap<String, Arc<Reference>>>, objects: Arc<HashMap<String, AddressableObject>>) -> Router {
|
2025-03-30 15:02:46 -04:00
|
|
|
Router::new().nest(
|
|
|
|
"/api/v1",
|
|
|
|
Router::new().nest(
|
|
|
|
"/ref",
|
|
|
|
Router::new()
|
2025-04-02 18:23:03 -04:00
|
|
|
.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)
|
|
|
|
}))
|
2025-03-30 15:02:46 -04:00
|
|
|
).nest(
|
|
|
|
"/object",
|
|
|
|
Router::new()
|
2025-04-02 18:23:03 -04:00
|
|
|
.route("/{addr}", get({
|
|
|
|
let objects = objects.clone();
|
2025-04-15 21:58:57 -04:00
|
|
|
move |addr| object_path(objects, addr)
|
|
|
|
}))
|
2025-03-30 15:02:46 -04:00
|
|
|
),
|
|
|
|
)
|
2025-03-30 21:18:08 -04:00
|
|
|
.route("/lib/client.js", get(get_client_js))
|
|
|
|
.route("/ui/", get(|| async { Html(include_str!("../static/index.html")).into_response() }))
|
2025-03-29 20:32:25 -04:00
|
|
|
}
|
|
|
|
|
2025-03-30 15:02:46 -04:00
|
|
|
// TODO(jwall): Javascript test script
|
2025-03-29 20:32:25 -04:00
|
|
|
pub async fn serve() {
|
|
|
|
// run our app with hyper, listening globally on port 3000
|
2025-04-02 18:23:03 -04:00
|
|
|
let (root_ref, refs, objects) = random_references_and_objects();
|
2025-03-30 15:02:46 -04:00
|
|
|
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000")
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2025-04-15 21:58:57 -04:00
|
|
|
println!("Server ui starting on http://127.0.0.1:3000/ui/");
|
2025-04-02 18:23:03 -04:00
|
|
|
axum::serve(listener, endpoints(root_ref, refs, objects)).await.unwrap();
|
2025-03-28 17:46:09 -04:00
|
|
|
}
|