1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
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 std::path::Path;
use crate::{
issues::issue::{Issue, Status},
SOURCE_CODE_REPOSITORY,
};
use format::BackString;
use gix::{refs::Target, Repository};
use issue_show::BackPrefix;
use rocket::{
get,
response::content::{RawCss, RawHtml},
};
use crate::REPOSITORY;
mod format;
mod issue;
mod issue_show;
#[get("/style.css")]
pub fn styles() -> RawCss<String> {
RawCss(include_str!("../../assets/style.css").to_owned())
}
fn list_all_issues(repo: &'_ Repository) -> Vec<Issue<'_>> {
repo.refs
.iter()
.expect("We should be able to iterate over references")
.prefixed(Path::new("refs/bugs/"))
.expect("The 'refs/bugs/' namespace should exist")
.map(|val| {
let reference = val.expect("'val' should be an object?");
if let Target::Object(commit_id) = reference.target {
Issue::from_commit_id(repo, commit_id)
} else {
unreachable!("All 'refs/bugs/' should contain a clear target.");
}
})
.collect()
}
pub fn issue_list_boilerplate(wanted_status: Status, counter_status: Status) -> RawHtml<String> {
let repository = REPOSITORY.get().expect("This should have been set");
let issue_list = list_all_issues(&repository.to_thread_local())
.iter()
.fold(String::new(), |acc, val| {
format!("{}{}", acc, &issue_to_string(val, wanted_status).0)
});
let counter_status_lower = counter_status.to_string().to_lowercase();
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}
</ol>
</main>
</div>
</body>
</html>
"#,
SOURCE_CODE_REPOSITORY.get().expect("This should be set")
))
}
#[get("/issues/open")]
pub fn open() -> RawHtml<String> {
issue_list_boilerplate(Status::Open, Status::Closed)
}
#[get("/issues/closed")]
pub fn closed() -> RawHtml<String> {
issue_list_boilerplate(Status::Closed, Status::Open)
}
#[get("/issue/<prefix>")]
pub fn show_issue(prefix: BackPrefix) -> RawHtml<String> {
let repository = REPOSITORY
.get()
.expect("This should have been set")
.to_thread_local();
let all_issues = list_all_issues(&repository);
let maybe_issue = all_issues
.iter()
.find(|issue| issue.id.to_string().starts_with(&prefix.to_string()));
match maybe_issue {
Some(issue) => issue.to_html(),
None => RawHtml(format!("Issue with id '{prefix}' not found!")),
}
}
fn issue_to_string(issue: &Issue<'_>, status: Status) -> RawHtml<String> {
if issue.status == status {
issue.to_list_entry()
} else {
RawHtml(String::default())
}
}
|