feat: add endpoint to enable email enrollment
This commit is contained in:
parent
255a6f1459
commit
46ab80a7ac
|
@ -5,7 +5,7 @@
|
|||
name = "pr-tracker"
|
||||
version = "1.2.0"
|
||||
authors = ["Alyssa Ross <hi@alyssa.is>"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
license = "AGPL-3.0-or-later WITH GPL-3.0-linking-exception"
|
||||
|
||||
[build-dependencies]
|
||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -23,6 +23,7 @@ use futures_util::future::join_all;
|
|||
use http_types::mime;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use structopt::StructOpt;
|
||||
use tide::{Request, Response};
|
||||
|
||||
|
@ -71,8 +72,10 @@ static GITHUB_TOKEN: Lazy<OsString> = Lazy::new(|| {
|
|||
struct PageTemplate {
|
||||
error: Option<String>,
|
||||
pr_number: Option<String>,
|
||||
email: Option<String>,
|
||||
pr_title: Option<String>,
|
||||
closed: bool,
|
||||
subscribed: bool,
|
||||
tree: Option<Tree>,
|
||||
source_url: String,
|
||||
}
|
||||
|
@ -80,14 +83,10 @@ struct PageTemplate {
|
|||
#[derive(Debug, Deserialize)]
|
||||
struct Query {
|
||||
pr: Option<String>,
|
||||
email: Option<String>,
|
||||
}
|
||||
|
||||
async fn track_pr(pr_number: Option<String>, status: &mut u16, page: &mut PageTemplate) {
|
||||
let pr_number = match pr_number {
|
||||
Some(pr_number) => pr_number,
|
||||
None => return,
|
||||
};
|
||||
|
||||
async fn track_pr(pr_number: String, status: &mut u16, page: &mut PageTemplate) {
|
||||
let pr_number_i64 = match pr_number.parse() {
|
||||
Ok(n) => n,
|
||||
Err(_) => {
|
||||
|
@ -146,8 +145,30 @@ async fn handle_request<S>(request: Request<S>) -> http_types::Result<Response>
|
|||
};
|
||||
|
||||
let pr_number = request.query::<Query>()?.pr;
|
||||
let email = request.query::<Query>()?.email;
|
||||
page.email = email.clone();
|
||||
|
||||
track_pr(pr_number, &mut status, &mut page).await;
|
||||
match pr_number.clone() {
|
||||
Some(pr_number) => track_pr(pr_number, &mut status, &mut page).await,
|
||||
None => {}
|
||||
};
|
||||
match email {
|
||||
Some(email) => {
|
||||
if let Some(ref tree) = page.tree {
|
||||
let mut v = Vec::new();
|
||||
let remaining = tree.collect_branches(&mut v);
|
||||
if !remaining {
|
||||
page.error = Some("There are no branches remaining to be tracked".to_string())
|
||||
} else {
|
||||
page.subscribed = true;
|
||||
let folder = format!("data/{}", pr_number.unwrap());
|
||||
std::fs::create_dir_all(folder.clone())?;
|
||||
std::fs::write(format!("{folder}/{email}"), json!(v).to_string())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
Ok(Response::builder(status)
|
||||
.content_type(mime::HTML)
|
||||
|
|
12
src/tree.rs
12
src/tree.rs
|
@ -39,6 +39,18 @@ impl Tree {
|
|||
children: nexts,
|
||||
}
|
||||
}
|
||||
pub fn collect_branches(&self, vec: &mut Vec<String>) -> bool {
|
||||
let mut res = false;
|
||||
if let Some(true) = self.accepted {
|
||||
vec.push(self.branch_name.clone());
|
||||
} else {
|
||||
res = true
|
||||
}
|
||||
for c in &self.children {
|
||||
res |= c.collect_branches(vec);
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn fill_accepted(&mut self, branches: &BTreeSet<OsString>, missing_means_absent: bool) {
|
||||
self.accepted = match branches.contains(OsStr::new(&self.branch_name)) {
|
||||
|
|
|
@ -4,212 +4,237 @@
|
|||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{% match pr_number %}
|
||||
{%- when Some with (pr_number) -%}
|
||||
{% match pr_title %}
|
||||
{%- when Some with (pr_title) -%}
|
||||
<title>Nixpkgs PR #{{ pr_number }} ("{{ pr_title }}") progress</title>
|
||||
{%- else -%}
|
||||
<title>Nixpkgs PR #{{ pr_number }} progress</title>
|
||||
{%- endmatch -%}
|
||||
{%- else -%}
|
||||
<title>Nixpkgs PR progress tracker</title>
|
||||
{% endmatch %}
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<head>
|
||||
{% match pr_number %}
|
||||
{%- when Some with (pr_number) -%}
|
||||
{% match pr_title %}
|
||||
{%- when Some with (pr_title) -%}
|
||||
<title>Nixpkgs PR #{{ pr_number }} ("{{ pr_title }}") progress</title>
|
||||
{%- else -%}
|
||||
<title>Nixpkgs PR #{{ pr_number }} progress</title>
|
||||
{%- endmatch -%}
|
||||
{%- else -%}
|
||||
<title>Nixpkgs PR progress tracker</title>
|
||||
{% endmatch %}
|
||||
|
||||
<style>
|
||||
:root {
|
||||
line-height: 1;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
body > header {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
<style>
|
||||
:root {
|
||||
line-height: 1;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#pr {
|
||||
width: 6ch;
|
||||
box-sizing: content-box;
|
||||
text-align: center;
|
||||
}
|
||||
body>header {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
body > section {
|
||||
background: #c4b0b0;
|
||||
padding: 0 1em;
|
||||
margin: 1em auto;
|
||||
display: flex;
|
||||
max-width: 50ch;
|
||||
}
|
||||
#pr {
|
||||
width: 6ch;
|
||||
box-sizing: content-box;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body > main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
body>section {
|
||||
background: #c4b0b0;
|
||||
padding: 0 1em;
|
||||
margin: 1em auto;
|
||||
display: flex;
|
||||
max-width: 50ch;
|
||||
}
|
||||
|
||||
body > main > ol {
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
body>main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
body>main>ol {
|
||||
text-align: left;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
ul > li {
|
||||
margin-left: 2em;
|
||||
position: relative;
|
||||
}
|
||||
ol,
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul > li:last-child {
|
||||
margin-left: 0;
|
||||
position: static;
|
||||
}
|
||||
ul>li {
|
||||
margin-left: 2em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
li {
|
||||
margin: 1em 0;
|
||||
line-height: 2;
|
||||
}
|
||||
ul>li:last-child {
|
||||
margin-left: 0;
|
||||
position: static;
|
||||
}
|
||||
|
||||
span {
|
||||
color: transparent;
|
||||
position: relative;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
li {
|
||||
margin: 1em 0;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
span::after {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
border: .3em solid #7A877D;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
span {
|
||||
color: transparent;
|
||||
position: relative;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
span.state-pending::after {
|
||||
background: #C2C9C2;
|
||||
}
|
||||
span::after {
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: block;
|
||||
border: .3em solid #7A877D;
|
||||
color: white;
|
||||
text-align: center;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
span.state-unknown::after {
|
||||
background: #C4A500;
|
||||
content: "?";
|
||||
}
|
||||
span.state-pending::after {
|
||||
background: #C2C9C2;
|
||||
}
|
||||
|
||||
span.state-accepted::after {
|
||||
background: #00C42D;
|
||||
content: "✔";
|
||||
}
|
||||
span.state-unknown::after {
|
||||
background: #C4A500;
|
||||
content: "?";
|
||||
}
|
||||
|
||||
span.state-rejected::after {
|
||||
background: #c40000;
|
||||
content: "❌︎";
|
||||
}
|
||||
span.state-accepted::after {
|
||||
background: #00C42D;
|
||||
content: "✔";
|
||||
}
|
||||
|
||||
ul span::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 42.5%;
|
||||
bottom: 42.5%;
|
||||
right: .5em;
|
||||
left: -1em;
|
||||
display: block;
|
||||
background: #7A877D;
|
||||
}
|
||||
.state-subscribed {
|
||||
background: #00C42D;
|
||||
margin-bottom: 1em;
|
||||
max-width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 1em;
|
||||
border-radius: 10px;
|
||||
border: 1px solid black;
|
||||
}
|
||||
|
||||
ul > li:last-child > span::before {
|
||||
content: none;
|
||||
}
|
||||
span.state-rejected::after {
|
||||
background: #c40000;
|
||||
content: "❌︎";
|
||||
}
|
||||
|
||||
ol {
|
||||
position: relative;
|
||||
}
|
||||
ul span::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 42.5%;
|
||||
bottom: 42.5%;
|
||||
right: .5em;
|
||||
left: -1em;
|
||||
display: block;
|
||||
background: #7A877D;
|
||||
}
|
||||
|
||||
ol::before, ul::before {
|
||||
background: #7A877D;
|
||||
content: "";
|
||||
display: block;
|
||||
left: .85em;
|
||||
top: 0.5em;
|
||||
bottom: 1em;
|
||||
width: .3em;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
ul>li:last-child>span::before {
|
||||
content: none;
|
||||
}
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Nixpkgs Pull Request Tracker</h1>
|
||||
ol {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
<form>
|
||||
<label for="pr">PR number: </label>
|
||||
<input id="pr" name="pr" type="text" pattern="[1-9][0-9]*"
|
||||
value="{%- match pr_number -%}
|
||||
ol::before,
|
||||
ul::before {
|
||||
background: #7A877D;
|
||||
content: "";
|
||||
display: block;
|
||||
left: .85em;
|
||||
top: 0.5em;
|
||||
bottom: 1em;
|
||||
width: .3em;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Nixpkgs Pull Request Tracker</h1>
|
||||
|
||||
{%- if subscribed -%}
|
||||
<div class="state-subscribed">You will be notified be by mail when this PR reaches a new ???</div>
|
||||
{%- endif -%}
|
||||
<form>
|
||||
<label for="pr">PR number: </label>
|
||||
<input id="pr" name="pr" type="text" pattern="[1-9][0-9]*" value="{%- match pr_number -%}
|
||||
{%- when Some with (pr_number) -%}
|
||||
{{- pr_number -}}
|
||||
{%- else -%}
|
||||
{%- endmatch -%}">
|
||||
<button type="submit">Track</button>
|
||||
</form>
|
||||
</header>
|
||||
<br>
|
||||
<label for="email">Email: </label>
|
||||
<input id="email" name="email" type="email" value="{%- match email -%}
|
||||
{%- when Some with (email) -%}
|
||||
{{- email -}}
|
||||
{%- else -%}
|
||||
{%- endmatch -%}">
|
||||
<br>
|
||||
<button type="submit">Track</button>
|
||||
</form>
|
||||
</header>
|
||||
|
||||
{% match error %}
|
||||
{% when Some with (error) %}
|
||||
<section>
|
||||
<p>{{ error }}</p>
|
||||
</section>
|
||||
{% else %}
|
||||
{% endmatch %}
|
||||
{% match error %}
|
||||
{% when Some with (error) %}
|
||||
<section>
|
||||
<p>{{ error }}</p>
|
||||
</section>
|
||||
{% else %}
|
||||
{% endmatch %}
|
||||
|
||||
{% match pr_number %}
|
||||
{%- when Some with (pr_number) -%}
|
||||
<main>
|
||||
<ol>
|
||||
<li>
|
||||
{%- if closed -%}
|
||||
<span class="state-rejected">❌</span>
|
||||
{%- else -%}
|
||||
<span class="state-accepted">✅</span>
|
||||
{%- endif -%}
|
||||
PR <a href="https://github.com/NixOS/nixpkgs/pull/{{ pr_number }}">#{{ pr_number }}</a>
|
||||
{% match pr_title %}
|
||||
{%- when Some with (pr_title) -%}
|
||||
("{{ pr_title }}")
|
||||
{%- else -%}
|
||||
{%- endmatch -%}
|
||||
{% if closed %}
|
||||
(closed)
|
||||
{%- endif -%}
|
||||
</li>
|
||||
{% match pr_number %}
|
||||
{%- when Some with (pr_number) -%}
|
||||
<main>
|
||||
<ol>
|
||||
<li>
|
||||
{%- if closed -%}
|
||||
<span class="state-rejected">❌</span>
|
||||
{%- else -%}
|
||||
<span class="state-accepted">✅</span>
|
||||
{%- endif -%}
|
||||
PR <a href="https://github.com/NixOS/nixpkgs/pull/{{ pr_number }}">#{{ pr_number }}</a>
|
||||
{% match pr_title %}
|
||||
{%- when Some with (pr_title) -%}
|
||||
("{{ pr_title }}")
|
||||
{%- else -%}
|
||||
{%- endmatch -%}
|
||||
{% if closed %}
|
||||
(closed)
|
||||
{%- endif -%}
|
||||
</li>
|
||||
|
||||
{% match tree %}
|
||||
{%- when Some with (tree) -%}
|
||||
{{- tree|safe -}}
|
||||
{%- else -%}
|
||||
{%- endmatch -%}
|
||||
</ol>
|
||||
</main>
|
||||
{%- else -%}
|
||||
{% endmatch %}
|
||||
|
||||
<footer>
|
||||
<p>By <a href="https://alyssa.is/">Alyssa Ross</a></p>
|
||||
{% match tree %}
|
||||
{%- when Some with (tree) -%}
|
||||
{{- tree|safe -}}
|
||||
{%- else -%}
|
||||
{%- endmatch -%}
|
||||
</ol>
|
||||
</main>
|
||||
{%- else -%}
|
||||
{% endmatch %}
|
||||
|
||||
<footer>
|
||||
<p>By <a href="https://alyssa.is/">Alyssa Ross</a></p>
|
||||
|
||||
<p><a href="{{ source_url }}">Source code</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
<p><a href="{{ source_url }}">Source code</a></p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Reference in a new issue