#![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())
}