use crate::{cache, config::Config, repo::ReadRepoMetadataError, templates}; use askama::Template; use log::error; use std::{ io::{ErrorKind, Read}, path::PathBuf, }; use trillium::{Conn, Handler}; use trillium_router::{Router, RouterConnExt}; 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(cache::Cache::< String, templates::Directory, >::default())); let file_cache: &'static _ = Box::leak(Box::new(cache::Cache::<(String, 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().with_max_len(8192).read_bytes().await { let mut repo_url = None; let mut commit_hash = None; for (key, val) in form_urlencoded::parse(&request_body) { 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 fetch_metadata = |key| { //let mut repo_hash = [0; 24]; //if dbg!(base64_turbo::URL_SAFE.decode_into(key, &mut repo_hash)) // != Ok(16) //{ // return None; //} //let repo_hash = &repo_hash[0..16]; let repo_dir = PathBuf::from(&config.data_dir) .join(crate::SUBDIR_REPOS) .join(key); 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 root = 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(); root.insert( templates::Entry::File(templates::File { name: name.into(), path: dbg!(entry.file_path).into(), hash: entry.hash.into(), }), path, ); } Err(e) => { error!("Reading repo metadata file index: {e:?}") } } } Some(root) }; let Some(root) = metadata_cache.fetch(repo_hash_str.to_string(), fetch_metadata) else { return conn.with_status(404); }; let Some(repo_file) = root.find(conn.path().split('/')) else { return conn.with_status(404); }; let fetch_file = |(repo_hash, file_hash)| { let file_path = PathBuf::from(&config.data_dir) .join(crate::SUBDIR_REPOS) .join(repo_hash) .join(file_hash); let mut file = match std::fs::OpenOptions::new().read(true).open(&file_path) { Ok(file) => file, Err(e) => { error!("Cannot open file `{file_path:?}`: {e:?}"); return None; } }; let mut buf = String::new(); if let Err(e) = file.read_to_string(&mut buf) { error!("Error reading file `{file_path:?}`: {e:?}"); return None; } Some(buf) }; let Some(file_content) = file_cache.fetch( (repo_hash_str.to_string(), repo_file.hash.clone()), fetch_file, ) else { return conn.with_status(500); }; let file_lang = conn .path() .rsplit('.') .next() .unwrap_or(giallo::PLAIN_GRAMMAR_NAME); let hl_options = giallo::HighlightOptions::new( file_lang, giallo::ThemeVariant::Single("catppuccin-frappe"), ); let highlighted = hl_registry.highlight(&file_content, &hl_options).unwrap_or_else(|_e| { // If extension is unknown let hl_options = giallo::HighlightOptions::new( giallo::PLAIN_GRAMMAR_NAME, giallo::ThemeVariant::Single("catppuccin-frappe"), ); hl_registry.highlight(&file_content, &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(), root, } .render() .unwrap()) } }) .get("/e/:secret", |conn: Conn| async move { conn.ok(crate::templates::Home {}.render().unwrap()) }), ) }