aboutsummaryrefslogtreecommitdiffstats
path: root/pkgs/by-name/ba/back/src/web
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ba/back/src/web')
-rw-r--r--pkgs/by-name/ba/back/src/web/generate/mod.rs225
-rw-r--r--pkgs/by-name/ba/back/src/web/mod.rs127
-rw-r--r--pkgs/by-name/ba/back/src/web/responses.rs50
3 files changed, 0 insertions, 402 deletions
diff --git a/pkgs/by-name/ba/back/src/web/generate/mod.rs b/pkgs/by-name/ba/back/src/web/generate/mod.rs
deleted file mode 100644
index ae783a3..0000000
--- a/pkgs/by-name/ba/back/src/web/generate/mod.rs
+++ /dev/null
@@ -1,225 +0,0 @@
-use std::{fs, path::Path};
-
-use gix::hash::Prefix;
-use log::info;
-use rinja::Template;
-use url::Url;
-
-use crate::{
- config::BackConfig,
- error,
- git_bug::{
- dag::issues_from_repository,
- issue::{CollapsedIssue, Status},
- },
-};
-
-#[derive(Template)]
-#[template(path = "./issues.html")]
-struct IssuesTemplate {
- wanted_status: Status,
- counter_status: Status,
- issues: Vec<CollapsedIssue>,
-
- /// The path to the repository
- repo_path: String,
-
- /// The URL to `back`'s source code
- source_code_repository_url: Url,
-}
-pub fn issues(
- config: &BackConfig,
- wanted_status: Status,
- counter_status: Status,
- repo_path: &Path,
-) -> error::Result<String> {
- let repository = config
- .repositories()?
- .get(repo_path)?
- .open(&config.scan_path)?;
-
- let mut issue_list = issues_from_repository(&repository.to_thread_local())?
- .into_iter()
- .map(|issue| issue.collapse())
- .filter(|issue| issue.status == wanted_status)
- .collect::<Vec<CollapsedIssue>>();
-
- // Sort by date descending.
- // SAFETY:
- // The time stamp is only used for sorting, so a malicious attacker could only affect the issue
- // sorting.
- issue_list.sort_by_key(|issue| unsafe { issue.timestamp.to_unsafe() });
- issue_list.reverse();
-
- Ok(IssuesTemplate {
- wanted_status,
- counter_status,
- source_code_repository_url: config.source_code_repository_url.clone(),
- issues: issue_list,
- repo_path: repo_path.display().to_string(),
- }
- .render()
- .expect("This should always work"))
-}
-
-use crate::git_bug::format::HtmlString;
-#[derive(Template)]
-#[template(path = "./issue.html")]
-struct IssueTemplate {
- issue: CollapsedIssue,
-
- /// The path to the repository
- repo_path: String,
-
- /// The URL to `back`'s source code
- source_code_repository_url: Url,
-}
-pub fn issue(config: &BackConfig, repo_path: &Path, prefix: Prefix) -> error::Result<String> {
- let repository = config
- .repositories()?
- .get(repo_path)?
- .open(&config.scan_path)?
- .to_thread_local();
-
- let maybe_issue = issues_from_repository(&repository)?
- .into_iter()
- .map(|val| val.collapse())
- .find(|issue| issue.id.to_string().starts_with(&prefix.to_string()));
-
- match maybe_issue {
- Some(issue) => Ok(IssueTemplate {
- issue,
- repo_path: repo_path.display().to_string(),
- source_code_repository_url: config.source_code_repository_url.clone(),
- }
- .render()
- .expect("This should always work")),
- None => Err(error::Error::IssuesPrefixMissing { prefix }),
- }
-}
-
-#[derive(Template)]
-#[template(path = "./repos.html")]
-struct ReposTemplate {
- repos: Vec<RepoValue>,
-
- /// The URL to `back`'s source code
- source_code_repository_url: Url,
-}
-struct RepoValue {
- description: String,
- owner: String,
- path: String,
-}
-pub fn repos(config: &BackConfig) -> error::Result<String> {
- let repos: Vec<RepoValue> = config
- .repositories()?
- .iter()
- .filter_map(|raw_repo| match raw_repo.open(&config.scan_path) {
- Ok(repo) => {
- let repo = repo.to_thread_local();
- let git_config = repo.config_snapshot();
-
- let path = raw_repo.path().to_string_lossy().to_string();
-
- let owner = git_config
- .string("cgit.owner")
- .map(|v| v.to_string())
- .unwrap_or("<No owner>".to_owned());
-
- let description = fs::read_to_string(repo.git_dir().join("description"))
- .unwrap_or("<No description>".to_owned());
-
- Some(RepoValue {
- description,
- owner,
- path,
- })
- }
- Err(err) => {
- info!(
- "Repo '{}' could not be opened: '{err}'",
- raw_repo.path().display()
- );
- None
- }
- })
- .collect();
-
- Ok(ReposTemplate {
- repos,
- source_code_repository_url: config.source_code_repository_url.clone(),
- }
- .render()
- .expect("this should work"))
-}
-
-pub fn feed(config: &BackConfig, repo_path: &Path) -> error::Result<String> {
- use rss::{ChannelBuilder, Item, ItemBuilder};
-
- let repository = config
- .repositories()?
- .get(repo_path)?
- .open(&config.scan_path)?
- .to_thread_local();
-
- let issues: Vec<CollapsedIssue> = issues_from_repository(&repository)?
- .into_iter()
- .map(|issue| issue.collapse())
- .collect();
-
- // Collect all Items as rss items
- let mut items: Vec<Item> = issues
- .iter()
- .map(|issue| {
- ItemBuilder::default()
- .title(issue.title.to_string())
- .author(issue.author.to_string())
- .description(issue.message.to_string())
- .pub_date(issue.timestamp.to_string())
- .link(format!(
- "/{}/{}/issue/{}",
- repo_path.display(),
- &config.root_url,
- issue.id
- ))
- .build()
- })
- .collect();
-
- // Append all comments after converting them to rss items
- items.extend(
- issues
- .iter()
- .filter(|issue| !issue.comments.is_empty())
- .flat_map(|issue| {
- issue
- .comments
- .iter()
- .map(|comment| {
- ItemBuilder::default()
- .title(issue.title.to_string())
- .author(comment.author.to_string())
- .description(comment.message.to_string())
- .pub_date(comment.timestamp.to_string())
- .link(format!(
- "/{}/{}/issue/{}",
- repo_path.display(),
- &config.root_url,
- issue.id
- ))
- .build()
- })
- .collect::<Vec<Item>>()
- })
- .collect::<Vec<Item>>(),
- );
-
- let channel = ChannelBuilder::default()
- .title("Issues")
- .link(config.root_url.to_string())
- .description(format!("The rss feed for issues on {}.", &config.root_url))
- .items(items)
- .build();
- Ok(channel.to_string())
-}
diff --git a/pkgs/by-name/ba/back/src/web/mod.rs b/pkgs/by-name/ba/back/src/web/mod.rs
deleted file mode 100644
index cc087ab..0000000
--- a/pkgs/by-name/ba/back/src/web/mod.rs
+++ /dev/null
@@ -1,127 +0,0 @@
-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<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);
- }
- });
- }
-}
diff --git a/pkgs/by-name/ba/back/src/web/responses.rs b/pkgs/by-name/ba/back/src/web/responses.rs
deleted file mode 100644
index e50f8c2..0000000
--- a/pkgs/by-name/ba/back/src/web/responses.rs
+++ /dev/null
@@ -1,50 +0,0 @@
-use std::convert::Infallible;
-
-use bytes::Bytes;
-use http::{Response, StatusCode, Version};
-use http_body_util::{combinators::BoxBody, BodyExt, Full};
-
-use crate::{error, git_bug::format::HtmlString};
-
-pub(super) fn html_response<T: Into<Bytes>>(html_text: T) -> Response<BoxBody<Bytes, Infallible>> {
- html_response_status(html_text, StatusCode::OK)
-}
-
-pub(super) fn html_response_status<T: Into<Bytes>>(
- html_text: T,
- status: StatusCode,
-) -> Response<BoxBody<Bytes, Infallible>> {
- html_response_status_content_type(html_text, status, "text/html")
-}
-
-pub(super) fn html_response_status_content_type<T: Into<Bytes>>(
- html_text: T,
- status: StatusCode,
- content_type: &str,
-) -> Response<BoxBody<Bytes, Infallible>> {
- Response::builder()
- .status(status)
- .version(Version::HTTP_2)
- .header("Content-Type", format!("{}; charset=utf-8", content_type))
- .header("x-content-type-options", "nosniff")
- .header("x-frame-options", "SAMEORIGIN")
- .body(full(html_text))
- .expect("This will always build")
-}
-
-fn full<T: Into<Bytes>>(chunk: T) -> BoxBody<Bytes, Infallible> {
- Full::new(chunk.into()).boxed()
-}
-
-// FIXME: Not all errors should return `INTERNAL_SERVER_ERROR`. <2025-03-08>
-impl error::Error {
- pub fn into_response(self) -> Response<BoxBody<Bytes, Infallible>> {
- html_response_status(
- format!(
- "<h1> Internal server error. </h1> <pre>Error: {}</pre>",
- HtmlString::from(self.to_string())
- ),
- StatusCode::INTERNAL_SERVER_ERROR,
- )
- }
-}