diff options
Diffstat (limited to 'pkgs/by-name/ba/back/src/web/mod.rs')
-rw-r--r-- | pkgs/by-name/ba/back/src/web/mod.rs | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/pkgs/by-name/ba/back/src/web/mod.rs b/pkgs/by-name/ba/back/src/web/mod.rs new file mode 100644 index 0000000..35dc59f --- /dev/null +++ b/pkgs/by-name/ba/back/src/web/mod.rs @@ -0,0 +1,134 @@ +// Back - An extremely simple git issue tracking system. Inspired by tvix's +// panettone +// +// Copyright (C) 2024 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 crate::{ + config::BackConfig, + error::{self, Error}, + git_bug::{ + dag::issues_from_repository, + issue::{CollapsedIssue, Status}, + }, +}; +use prefix::BackPrefix; +use rocket::{ + get, + response::content::{RawCss, RawHtml}, + State, +}; + +mod issue_html; +pub mod prefix; + +#[get("/style.css")] +pub fn styles() -> RawCss<String> { + RawCss(include_str!("../../assets/style.css").to_owned()) +} + +pub fn issue_list_boilerplate( + config: &State<BackConfig>, + wanted_status: Status, + counter_status: Status, +) -> error::Result<RawHtml<String>> { + let repository = &config.repository; + + let mut issue_list = issues_from_repository(&repository.to_thread_local())? + .into_iter() + .map(|issue| issue.collapse()) + .collect::<Vec<CollapsedIssue>>(); + + // Sort by date descending. + issue_list.sort_by_key(|issue| unsafe { issue.timestamp.to_unsafe() }); + issue_list.reverse(); + + let issue_list_str = issue_list.into_iter().fold(String::new(), |acc, issue| { + format!("{}{}", acc, { + if issue.status == wanted_status { + let issue_entry = issue.to_list_entry(); + issue_entry.0 + } else { + String::new() + } + }) + }); + + let counter_status_lower = counter_status.to_string().to_lowercase(); + Ok(RawHtml(format!( + r#" + <!DOCTYPE html> + <html lang="en"> + <head> + <title>Back</title> + <link href="/style.css" rel="stylesheet" type="text/css"> + <meta content="width=device-width,initial-scale=1" name="viewport"> + </head> + <body> + <div class="content"> + <header> + <h1>{wanted_status} Issues</h1> + </header> + <main> + <div class="issue-links"> + <a href="/issues/{counter_status_lower}/">View {counter_status} issues</a> + <a href="{}">Source code</a> + <!-- + <form class="issue-search" method="get"> + <input name="search" title="Issue search query" type="search"> + <input class="sr-only" type="submit" value="Search Issues"> + </form> + --> + </div> + <ol class="issue-list"> + {issue_list_str} + </ol> + </main> + </div> + </body> + </html> + "#, + config.source_code_repository_url + ))) +} + +#[get("/issues/open")] +pub fn open(config: &State<BackConfig>) -> error::Result<RawHtml<String>> { + issue_list_boilerplate(config, Status::Open, Status::Closed) +} +#[get("/issues/closed")] +pub fn closed(config: &State<BackConfig>) -> error::Result<RawHtml<String>> { + issue_list_boilerplate(config, Status::Closed, Status::Open) +} + +#[get("/issue/<prefix>")] +pub fn show_issue( + config: &State<BackConfig>, + prefix: Result<BackPrefix, gix::hash::prefix::from_hex::Error>, +) -> error::Result<RawHtml<String>> { + // NOTE(@bpeetz): Explicitly unwrap the `prefix` here (instead of taking the unwrapped value as + // argument), to avoid triggering rockets "errors forward to the next route" feature. + // This ensures, that our error message actually reaches the user. <2024-12-26> + let prefix = prefix?; + + let repository = config.repository.to_thread_local(); + + let all_issues: Vec<CollapsedIssue> = issues_from_repository(&repository)? + .into_iter() + .map(|val| val.collapse()) + .collect(); + + let maybe_issue = all_issues + .iter() + .find(|issue| issue.id.to_string().starts_with(&prefix.to_string())); + + match maybe_issue { + Some(issue) => Ok(issue.to_html(config)), + None => Err(Error::IssuesPrefixMissing { prefix }), + } +} |