about summary refs log tree commit diff stats
path: root/pkgs/by-name/ba/back/src/web/generate
diff options
context:
space:
mode:
Diffstat (limited to 'pkgs/by-name/ba/back/src/web/generate')
-rw-r--r--pkgs/by-name/ba/back/src/web/generate/mod.rs227
1 files changed, 227 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..10146bb
--- /dev/null
+++ b/pkgs/by-name/ba/back/src/web/generate/mod.rs
@@ -0,0 +1,227 @@
+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.repositories.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.repositories.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.repositories.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.repositories.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,
+                    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,
+                                issue.id
+                            ))
+                            .build()
+                    })
+                    .collect::<Vec<Item>>()
+            })
+            .collect::<Vec<Item>>(),
+    );
+
+    let channel = ChannelBuilder::default()
+        .title("Issues")
+        .link(config.root.to_string())
+        .description(format!("The rss feed for issues on {}.", &config.root))
+        .items(items)
+        .build();
+    Ok(channel.to_string())
+}