#![forbid(unsafe_code)] pub mod code_count; pub mod file; pub mod listing; pub mod r#static; pub mod user; use actix_web::{ get, http::header::{HeaderValue, LOCATION}, web, HttpResponse, Responder, }; use askama::Template; use log::{error, warn}; use serde::Deserialize; use std::collections::HashMap; use std::sync::Mutex; #[derive(Deserialize, Debug)] pub struct RepoConfig { pub description: String, pub clone_with: Option<String>, } #[derive(Deserialize, Debug)] pub struct UserConfig { pub repos: HashMap<String, RepoConfig>, } #[derive(Deserialize, Debug)] pub struct Config { pub users: HashMap<String, UserConfig>, pub repos_dir: String, pub folder_depth: usize, } #[derive(Debug)] pub struct AppState { pub config: Config, // TODO: make this some sort of evicting cache that can't grow boundlessly. pub line_counts: Mutex<HashMap<String, (code_count::RepoLanguageStats, usize)>>, } #[derive(Template)] #[template(path = "index.html")] struct IndexHtml { users: Vec<String>, } #[get("/")] pub(crate) async fn get_index(app_data: web::Data<AppState>) -> impl Responder { let num_users = app_data.config.users.len(); let users: Vec<_> = app_data .config .users .iter() .map(|(k, _)| k.clone()) .collect(); if num_users == 1 { if let Ok(header_value) = HeaderValue::from_str(&format!("/~{}", users[0])) { let mut redirect = HttpResponse::PermanentRedirect().body(()); redirect.headers_mut().insert(LOCATION, header_value); return redirect; } } println!("asdf {:?}", app_data.config); let rendered = IndexHtml { users }.render(); match rendered { Ok(content) => HttpResponse::Ok().content_type("text/html").body(content), _ => HttpResponse::InternalServerError().body("Error rendering index"), } } impl Config { pub fn get_repo_description(&self, username: &str, repo_name: &str) -> String { if let Some(user_config) = self.users.get(username) { if let Some(repo_config) = user_config.repos.get(repo_name) { return repo_config.description.clone(); } }; return String::from(""); } pub fn get_repo_config(&self, username: &str, repo_name: &str) -> Option<&RepoConfig> { self.users .get(username) .and_then(|user| user.repos.get(repo_name)) } } pub fn get_config(filename: &Option<String>) -> Config { let empty_config = Config { users: HashMap::new(), repos_dir: "./".to_string(), folder_depth: 4, }; let file = match filename { Some(x) => x, _ => { warn!("no config filename {:?}", filename); return empty_config; } }; let config_toml = match std::fs::read_to_string(&file) { Ok(x) => x, Err(x) => { warn!("could not read config {}", x); return empty_config; } }; match toml::from_str(&config_toml) { Ok(x) => x, Err(x) => { error!("Could not parse config file: {:?}", x); return empty_config; } } } pub fn guard_username_configured( username: &str, app_data: &web::Data<AppState>, ) -> Option<HttpResponse> { let username_configured = app_data.config.users.get(username).is_some(); if username_configured { return None; } Some(HttpResponse::NotFound().body(render_404( format!("404 - {}", username), format!("Could not find user {}", username), ))) } #[derive(Template)] #[template(path = "not_found.html")] struct NotFoundHtml { title: String, description: String, } pub fn render_404(title: String, description: String) -> String { NotFoundHtml { title, description } .render() .unwrap_or("404 not found".to_string()) }