about summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/web/generate/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ba/back/src/web/generate/mod.rs')
-rw-r--r--pkgs/by-name/ba/back/src/web/generate/mod.rs225
1 files changed, 225 insertions, 0 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
new file mode 100644
index 0000000..ae783a3
--- /dev/null
+++ b/pkgs/by-name/ba/back/src/web/generate/mod.rs
@@ -0,0 +1,225 @@
+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())
+}