diff options
Diffstat (limited to 'src/web/mod.rs')
-rw-r--r-- | src/web/mod.rs | 138 |
1 files changed, 138 insertions, 0 deletions
diff --git a/src/web/mod.rs b/src/web/mod.rs new file mode 100644 index 0000000..8e2e9b0 --- /dev/null +++ b/src/web/mod.rs @@ -0,0 +1,138 @@ +// Back - An extremely simple git bug visualization system. Inspired by TVL's +// panettone. +// +// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This file is part of Back. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/agpl.txt>. + +use std::{convert::Infallible, net::SocketAddr, path::PathBuf, sync::Arc}; + +use bytes::Bytes; +use http_body_util::combinators::BoxBody; +use hyper::{Method, Request, Response, StatusCode, server::conn::http1, service::service_fn}; +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 crate::{config::BackConfig, error, git_bug::issue::Status}; + +mod generate; +mod responses; + +async fn match_uri( + config: Arc<BackConfig>, + req: Request<hyper::body::Incoming>, +) -> Result<Response<BoxBody<Bytes, Infallible>>, hyper::Error> { + if req.method() != Method::GET { + return Ok(html_response_status( + "Only get requests are supported", + StatusCode::NOT_ACCEPTABLE, + )); + } + + let output = || -> Result<Response<BoxBody<Bytes, Infallible>>, 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<BackConfig>) -> 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); + } + }); + } +} |