blindforge/src/server.rs
2026-03-29 12:00:57 +02:00

160 lines
4.5 KiB
Rust

use crate::{cache, config::Config, repo::ReadRepoMetadataError, templates};
use askama::Template;
use async_lock::Mutex;
use log::error;
use std::{
collections::{BTreeMap, BTreeSet, HashMap},
io::ErrorKind,
path::PathBuf,
};
use trillium::{Conn, Handler};
use trillium_router::{Router, RouterConnExt};
pub async fn hello_world(conn: Conn) -> Conn {
conn.ok("hello world!")
}
pub fn make_router(config: &'static Config) -> impl Handler {
let mut hl_registry = giallo::Registry::builtin().unwrap();
hl_registry.link_grammars();
let hl_registry: &'static _ = Box::leak(Box::new(hl_registry));
let metadata_cache: &'static _ = Box::leak(Box::new(Mutex::new(cache::Cache::<
String,
HashMap<String, String>,
>::default())));
let client: &'static _ = Box::leak(Box::new(async_lock::Mutex::new(
crate::api_client::make_client(),
)));
(
trillium_caching_headers::CachingHeaders::new(),
//trillium_static_compiled::static_compiled!("./static").with_index_file("index.html"),
Router::new()
.get("/", |conn: Conn| async move {
conn.ok(crate::templates::Home {}.render().unwrap())
})
.post("/fetch", move |mut conn: Conn| async move {
if let Ok(request_body) = conn.request_body().await.with_max_len(8192).await {
let mut repo_url = None;
let mut commit_hash = None;
for (key, val) in form_urlencoded::parse(request_body.as_bytes()) {
match key.as_ref() {
"repo-url" => {
repo_url = Some(val);
}
"commit" => {
commit_hash = Some(val);
}
_ => {}
}
}
let (Some(repo_url), Some(commit_hash)) = (repo_url, commit_hash) else {
return conn.ok("Missing arg");
};
let mut client = client.lock().await;
let (repo_index, mut repo_metadata) =
crate::api_client::fetch_repo_tree_index_at_commit(
&mut client,
&repo_url,
&commit_hash,
None,
)
.await
.expect("todo handle error");
crate::api_client::fetch_repo_files(
config,
&mut client,
&repo_index,
&mut repo_metadata,
None,
)
.await
.expect("todo handle error");
}
//let planet = conn.param("planet").unwrap();
conn.ok(crate::templates::Home {}.render().unwrap())
})
.get("/r/:hash/*", move |conn: Conn| {
async move {
let Some(repo_hash_str) = conn.param("hash") else {
return conn.with_status(401);
};
let cache_fetch = |key| {
let mut repo_hash = [0; 32];
if base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash) != Ok(32) {
return None;
}
let repo_dir = PathBuf::from(&config.data_dir)
.join(crate::SUBDIR_REPOS)
.join(repo_hash_str);
let repo_metadata =
match crate::repo::RepoMetadata::read_from_file(config, &repo_dir) {
Ok(v) => v,
Err(e) => {
if let ReadRepoMetadataError::CannotOpenFile(e) = &e {
if e.kind() == ErrorKind::NotFound {
return None;
}
}
error!("Reading repo metadata: {e:?}");
return None;
}
};
let mut entries = templates::Directory::default();
for entry in repo_metadata.iter_files() {
match entry {
Ok(entry) => {
let Some(name) = entry.file_path.rsplit('/').next() else {
error!("Entry has no name");
continue;
};
let path = entry.file_path.split('/').peekable();
entries.insert(entry, path);
}
Err(e) => {
error!("Reading repo metadata file index: {e:?}")
}
}
}
Some(files)
};
// TODO replace mutex with better thing (less contention or async mutex)
let Some(metadata) = metadata_cache
.lock()
.await
.fetch(repo_hash_str.to_string(), cache_fetch)
else {
return conn.with_status(404);
};
let hl_options = giallo::HighlightOptions::new(
"py",
giallo::ThemeVariant::Single("catppuccin-frappe"),
);
let highlighted = hl_registry
.highlight("def foo():\n\tpass", &hl_options)
.unwrap();
let html = giallo::HtmlRenderer::default().render(
&highlighted,
&giallo::RenderOptions {
show_line_numbers: true,
..Default::default()
},
);
conn.ok(crate::templates::Repo {
content: html.clone(),
entries: Vec::new(),
}
.render()
.unwrap())
}
})
.get("/e/:secret", |conn: Conn| async move {
conn.ok(crate::templates::Home {}.render().unwrap())
}),
)
}