use bytes::Bytes; use http_body_util::combinators::BoxBody; use hyper::{server::conn::http1, service::service_fn, Method, Request, Response, StatusCode}; use hyper_util::rt::TokioIo; use log::{error, info}; use responses::{html_response, html_response_status, html_response_status_content_type}; use tokio::net::TcpListener; use std::{convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc}; use crate::{config::BackConfig, error, git_bug::issue::Status}; mod generate; mod responses; async fn match_uri( config: Arc, req: Request, ) -> Result>, hyper::Error> { if req.method() != Method::GET { return Ok(html_response_status( "Only get requests are supported", StatusCode::NOT_ACCEPTABLE, )); } let output = || -> Result>, error::Error> { match req.uri().path().trim_end_matches("/") { "" => Ok(html_response(generate::repos(&config)?)), "/style.css" => Ok(responses::html_response_status_content_type( include_str!("../../assets/style.css"), StatusCode::OK, "text/css", )), path if path.ends_with("/issues/open") => { let repo_path = PathBuf::from( path.strip_suffix("/issues/open") .expect("This suffix exists") .strip_prefix("/") .expect("This also exists"), ); let issues = generate::issues(&config, Status::Open, Status::Closed, &repo_path)?; Ok(html_response(issues)) } path if path.ends_with("/issues/closed") => { let repo_path = PathBuf::from( path.strip_suffix("/issues/closed") .expect("This suffix exists") .strip_prefix("/") .expect("This also exists"), ); let issues = generate::issues(&config, Status::Closed, Status::Open, &repo_path)?; Ok(html_response(issues)) } path if path.ends_with("/issues/feed") => { let repo_path = PathBuf::from( path.strip_suffix("/issues/feed") .expect("This suffix exists") .strip_prefix("/") .expect("This also exists"), ); let feed = generate::feed(&config, &repo_path)?; Ok(html_response_status_content_type( feed, StatusCode::OK, "text/xml", )) } path if path.contains("/issue/") => { let (repo_path, prefix) = { let split: Vec<&str> = path.split("/issue/").collect(); let prefix = gix::hash::Prefix::from_hex(split[1]).map_err(error::Error::from)?; let repo_path = PathBuf::from(split[0].strip_prefix("/").expect("This prefix exists")); (repo_path, prefix) }; Ok(html_response(generate::issue(&config, &repo_path, prefix)?)) } other => Ok(responses::html_response_status_content_type( format!("'{}' not found", other), StatusCode::NOT_FOUND, "text/plain", )), } }; match output() { Ok(response) => Ok(response), Err(err) => Ok(err.into_response()), } } pub async fn main(config: Arc) -> Result<(), error::Error> { let addr: SocketAddr = ([127, 0, 0, 1], 8000).into(); let listener = TcpListener::bind(addr) .await .map_err(|err| error::Error::TcpBind { addr, err })?; info!("Listening on http://{}", addr); loop { let (stream, _) = listener .accept() .await .map_err(|err| error::Error::TcpAccept { err })?; let io = TokioIo::new(stream); let local_config = Arc::clone(&config); let service = service_fn(move |req| match_uri(Arc::clone(&local_config), req)); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io, service).await { error!("Error serving connection: {:?}", err); } }); } }