From 050e488ffe0449faba8dc9e98feb37aa908d536c Mon Sep 17 00:00:00 2001 From: apiraino Date: Tue, 12 Sep 2023 15:35:15 +0200 Subject: [PATCH 01/10] Add pull request preferences backoffice --- src/github.rs | 64 +++++++ src/lib.rs | 185 +++++++++++++++++++- src/main.rs | 272 ++++++++++++++++++++++++++++- src/triage.rs | 31 +++- static/git-dark.svg | 1 + static/git-light.svg | 1 + templates/pr-prefs-backoffice.html | 193 ++++++++++++++++++++ 7 files changed, 744 insertions(+), 3 deletions(-) create mode 100644 static/git-dark.svg create mode 100644 static/git-light.svg create mode 100644 templates/pr-prefs-backoffice.html diff --git a/src/github.rs b/src/github.rs index 496a214e..43a64f02 100644 --- a/src/github.rs +++ b/src/github.rs @@ -167,6 +167,23 @@ impl GithubClient { let (body, _req_dbg) = self.send_req(req).await?; Ok(serde_json::from_slice(&body)?) } + + pub async fn get_team_members<'a>( + &self, + admins: &mut Vec, + members: &mut Vec, + team: &str, + ) { + let req = self.get(&format!( + "https://raw.githubusercontent.com/rust-lang/team/HEAD/teams/{}", + team, + )); + let (contents, _) = self.send_req(req).await.unwrap(); + let body = String::from_utf8_lossy(&contents).to_string(); + let mut config: crate::Config = toml::from_str(&body).unwrap(); + members.append(&mut config.people.members); + admins.append(&mut config.people.leads); + } } impl User { @@ -2046,6 +2063,53 @@ impl GithubClient { .await .with_context(|| format!("{} failed to get repo", full_name)) } + + pub async fn get_profile(&self, access_token: &str) -> anyhow::Result { + let c = self + .client + .get("https://api.github.com/user") + .header("Authorization", format!("Bearer {}", access_token)) + .header("User-Agent", "rust-lang-triagebot"); + self.json::(c).await + } +} + +pub async fn exchange_code( + code: &str, + client_id: &str, + client_secret: &str, +) -> anyhow::Result { + let client = reqwest::Client::new(); + let payload = + serde_json::json!({"client_id":client_id, "client_secret":client_secret, "code":code}); + + let tk = client + .post("https://github.com/login/oauth/access_token") + .header("Accept", "application/json") + .json(&payload) + .send() + .await + .context("Failed to contact remote host") + .unwrap() + .json::() + .await + .context("Could not decode response") + .expect("Error while retrieving the GH token"); + + if let Some(err_msg) = tk.get("error_description").cloned() { + return Err(anyhow::Error::msg(err_msg)); + } + Ok(tk + .get("access_token") + .unwrap() + .to_string() + .replace("\"", "")) +} + +pub async fn get_gh_user(access_token: &str) -> anyhow::Result { + let client = reqwest::Client::new(); + let gh = GithubClient::new(client, access_token.to_string()); + gh.get_profile(access_token).await } #[derive(Debug, serde::Deserialize)] diff --git a/src/lib.rs b/src/lib.rs index aafa5eea..58b65135 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,8 @@ use crate::github::PullRequestDetails; use anyhow::Context; use handlers::HandlerError; use interactions::ErrorComment; -use std::fmt; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, fmt}; use tracing as log; pub mod actions; @@ -261,3 +262,185 @@ pub async fn webhook( Ok(true) } } + +const PREF_ALLOW_PING_AFTER_DAYS: i32 = 20; +const PREF_MAX_ASSIGNED_PRS: i32 = 5; + +#[derive(Debug, Serialize)] +pub struct ReviewCapacityUser { + pub username: String, + pub id: uuid::Uuid, + pub checksum: String, + pub user_id: i64, + pub assigned_prs: Vec, + pub num_assigned_prs: Option, + pub max_assigned_prs: Option, + pub pto_date_start: Option, + pub pto_date_end: Option, + pub active: bool, + pub allow_ping_after_days: Option, + pub publish_prefs: bool, +} + +impl ReviewCapacityUser { + pub(crate) fn is_available(&self) -> bool { + let today = chrono::Utc::today().naive_utc(); + let is_available = (self.pto_date_end.is_some() && self.pto_date_start.is_some()) + && (self.pto_date_end.unwrap() < today || self.pto_date_start.unwrap() > today); + self.active && is_available + } + + // thin compat. layer, will be removed after stabilizing the new PR assignment + fn phony(username: String) -> Self { + Self { + username, + id: uuid::Uuid::new_v4(), + user_id: -1, + checksum: String::new(), + assigned_prs: vec![], + num_assigned_prs: None, + max_assigned_prs: None, + pto_date_start: None, + pto_date_end: None, + active: false, + allow_ping_after_days: None, + publish_prefs: false, + } + } +} + +impl From> for ReviewCapacityUser { + fn from(obj: HashMap) -> Self { + // Note: if user set themselves as inactive all other prefs will be ignored + let active = { + let _o = obj.get("active"); + if _o.is_none() || _o.unwrap() == "no" { + false + } else { + true + } + }; + + let assigned_prs = if active { + if let Some(x) = obj.get("assigned_prs") { + x.parse::() + .unwrap() + .split(",") + .map(|x| x.parse::().unwrap()) + .collect() + } else { + vec![] + } + } else { + vec![] + }; + + let num_assigned_prs = if active { + if let Some(x) = obj.get("num_assigned_prs") { + Some(x.parse::().unwrap()) + } else { + None + } + } else { + None + }; + + let max_assigned_prs = if active { + if let Some(x) = obj.get("max_assigned_prs") { + Some(x.parse::().unwrap_or(PREF_MAX_ASSIGNED_PRS)) + } else { + None + } + } else { + None + }; + + let pto_date_start = if active { + if let Some(x) = obj.get("pto_date_start") { + Some(x.parse::().unwrap()) + } else { + None + } + } else { + None + }; + + let pto_date_end = if active { + if let Some(x) = obj.get("pto_date_end") { + Some(x.parse::().unwrap()) + } else { + None + } + } else { + None + }; + + let allow_ping_after_days = if active { + if let Some(x) = obj.get("allow_ping_after_days") { + Some(x.parse::().unwrap_or(PREF_ALLOW_PING_AFTER_DAYS)) + } else { + None + } + } else { + None + }; + + let publish_prefs = { + let _obj = obj.get("publish_prefs"); + if _obj.is_none() || _obj.unwrap() == "no" { + false + } else { + true + } + }; + + let prefs = ReviewCapacityUser { + // fields we don't receive from the web form (they are here ignored) + username: String::new(), + id: uuid::Uuid::new_v4(), + checksum: String::new(), + // fields received from the web form + user_id: obj.get("user_id").unwrap().parse::().unwrap(), + assigned_prs, + num_assigned_prs, + max_assigned_prs, + pto_date_start, + pto_date_end, + active, + allow_ping_after_days, + publish_prefs, + }; + prefs + } +} + +impl From for ReviewCapacityUser { + fn from(row: tokio_postgres::row::Row) -> Self { + Self { + username: row.get("username"), + id: row.get("id"), + checksum: row.get("checksum"), + user_id: row.get("user_id"), + assigned_prs: row.get("assigned_prs"), + num_assigned_prs: row.get("num_assigned_prs"), + max_assigned_prs: row.get("max_assigned_prs"), + pto_date_start: row.get("pto_date_start"), + pto_date_end: row.get("pto_date_end"), + active: row.get("active"), + allow_ping_after_days: row.get("allow_ping_after_days"), + publish_prefs: row.get("publish_prefs"), + } + } +} + +// Used to deserialize .toml from GitHub +#[derive(Deserialize)] +struct Config { + people: People, +} + +#[derive(Deserialize)] +struct People { + leads: Vec, + members: Vec, +} diff --git a/src/main.rs b/src/main.rs index 92fda321..d9db379f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,40 @@ #![allow(clippy::new_without_default)] use anyhow::Context as _; +use chrono::{Duration, Utc}; +use crypto_hash::{hex_digest, Algorithm}; +use flate2::{write::GzEncoder, Compression}; use futures::future::FutureExt; use futures::StreamExt; use hyper::{header, Body, Request, Response, Server, StatusCode}; use reqwest::Client; use route_recognizer::Router; +use std::collections::HashMap; +use std::io::Write; use std::{env, net::SocketAddr, sync::Arc}; use tokio::{task, time}; use tower::{Service, ServiceExt}; use tracing as log; use tracing::Instrument; +use triagebot::actions::TEMPLATES; +use triagebot::github::User; +use triagebot::handlers::review_prefs::get_user; use triagebot::jobs::{jobs, JOB_PROCESSING_CADENCE_IN_SECS, JOB_SCHEDULING_CADENCE_IN_SECS}; -use triagebot::{db, github, handlers::Context, notification_listing, payload, EventName}; +use triagebot::ReviewCapacityUser; +use triagebot::{ + db, github, + handlers::review_prefs::{get_prefs, set_prefs}, + handlers::Context, + notification_listing, payload, EventName, +}; + +const TINFRA_ZULIP_LINK: &str = "https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra"; +const ERR_AUTH : &str = "Fatal error occurred during authentication. Please click here to retry. \ +

If the error persists, please contact an administrator on Zulip and provide your GitHub username."; +const ERR_NO_GH_USER: &str = "Fatal error: cannot load the backoffice. Please contact an administrator on Zulip and provide your GitHub username. \ + "; +const ERR_AUTH_UNAUTHORIZED : &str = "You are not allowed to enter this website. \ +

If you think this is a mistake, please contact an administrator on Zulip and provide your GitHub username."; async fn handle_agenda_request(req: String) -> anyhow::Result { if req == "/agenda/lang/triage" { @@ -25,6 +47,15 @@ async fn handle_agenda_request(req: String) -> anyhow::Result { anyhow::bail!("Unknown agenda; see /agenda for index.") } +fn validate_data(prefs: &ReviewCapacityUser) -> anyhow::Result<()> { + if prefs.pto_date_start > prefs.pto_date_end { + return Err(anyhow::anyhow!( + "pto_date_start cannot be bigger than pto_date_end" + )); + } + Ok(()) +} + async fn serve_req( req: Request, ctx: Arc, @@ -34,9 +65,15 @@ async fn serve_req( let mut router = Router::new(); router.add("/triage", "index".to_string()); router.add("/triage/:owner/:repo", "pulls".to_string()); + router.add("/static/:file", "static-assets".to_string()); let (req, body_stream) = req.into_parts(); if let Ok(matcher) = router.recognize(req.uri.path()) { + if matcher.handler().as_str() == "static-assets" { + let params = matcher.params(); + let file = params.find("file"); + return triagebot::triage::asset(file.unwrap()).await; + } if matcher.handler().as_str() == "pulls" { let params = matcher.params(); let owner = params.find("owner"); @@ -145,6 +182,230 @@ async fn serve_req( .body(Body::from(triagebot::zulip::respond(&ctx, req).await)) .unwrap()); } + + if req.uri.path() == "/review-settings" { + let mut members = vec![]; + // yes, I am an hardcoded admin + let mut admins = vec!["apiraino".to_string()]; + let gh = github::GithubClient::new_with_default_token(Client::new()); + + // check if we received a session cookie + let maybe_user_enc = match req.headers.get("Cookie") { + Some(cookie_string) => cookie_string + .to_str() + .unwrap() + .split(';') + .filter_map(|cookie| { + let c = cookie.split('=').map(|x| x.trim()).collect::>(); + if c[0] == "triagebot.session".to_string() { + Some(c[1]) + } else { + None + } + }) + .last(), + _ => None, + }; + + let db_client = ctx.db.get().await; + let user = match maybe_user_enc { + Some(user_enc) => { + // We have a user in the cookie + // Verify who this user claims to be + // format is: {"checksum":"...", "exp":"...", "sub":"...", "uid":"..."} + let user_check: serde_json::Value = serde_json::from_str(user_enc).unwrap(); + let basic_check = create_cookie_content( + user_check["sub"].as_str().unwrap(), + user_check["uid"].as_i64().unwrap(), + ); + if basic_check["checksum"] != user_check["checksum"] { + return Ok(Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::from( + ERR_AUTH.replace("{tinfra_zulip_link}", TINFRA_ZULIP_LINK), + )) + .unwrap()); + } + + match get_user(&db_client, user_check["checksum"].as_str().unwrap()).await { + Ok(u) => User { + login: u.username, + id: Some(u.user_id), + }, + Err(err) => { + log::debug!("{:?}", err); + return Ok(Response::builder() + .status(StatusCode::FORBIDDEN) + .body(Body::empty()) + .unwrap()); + } + } + } + _ => { + // No username. Did we receive a `code` in the query URL (i.e. did the user went through the GH auth)? + let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID is not set"); + let client_secret = + std::env::var("CLIENT_SECRET").expect("CLIENT_SECRET is not set"); + if let Some(query) = req.uri.query() { + let code = + url::form_urlencoded::parse(query.as_bytes()).find(|(k, _)| k == "code"); + let login_link = format!( + "https://github.com/login/oauth/authorize?client_id={}", + client_id + ); + if let Some((_, code)) = code { + // generate a token to impersonate the user + let maybe_access_token = + github::exchange_code(&code, &client_id, &client_secret).await; + if let Err(err_msg) = maybe_access_token { + log::debug!("Github auth failed: {}", err_msg); + return Ok(Response::builder() + .status(StatusCode::OK) + .header(hyper::header::CONTENT_TYPE, "text/html") + .body(Body::from( + ERR_AUTH + .replace("{login_link}", &login_link) + .replace("{}", TINFRA_ZULIP_LINK), + )) + .unwrap()); + } + let access_token = maybe_access_token.unwrap(); + // Ok, we have an access token. Retrieve the GH username + match github::get_gh_user(&access_token).await { + Ok(user) => user, + Err(err) => { + log::debug!("Could not retrieve the user from GH: {:?}", err); + return Ok(Response::builder() + .status(StatusCode::OK) + .header(hyper::header::CONTENT_TYPE, "text/html") + .body(Body::from( + ERR_NO_GH_USER.replace("{}", TINFRA_ZULIP_LINK), + )) + .unwrap()); + } + } + } else { + // no code. Bail out + return Ok(Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::from( + ERR_AUTH + .replace("{login_link}", &login_link) + .replace("{tinfra_login_link}", TINFRA_ZULIP_LINK), + )) + .unwrap()); + } + } else { + // no code and no username received: we know nothing about this visitor. Redirect to GH login. + return Ok(Response::builder() + .status(StatusCode::MOVED_PERMANENTLY) + .header( + hyper::header::LOCATION, + format!( + "https://github.com/login/oauth/authorize?client_id={}", + client_id + ), + ) + .body(Body::empty()) + .unwrap()); + } + } + }; + + // Here we have a validated username. From now on, we will trust this user + let is_admin = admins.contains(&user.login); + log::debug!("current user={}, is admin: {}", user.login, is_admin); + + // get team members from github (HTTP raw file retrieval, no auth used) + // TODO: maybe add some kind of caching for these files + let x = std::env::var("NEW_PR_ASSIGNMENT_TEAMS") + .expect("NEW_PR_ASSIGNMENT_TEAMS env var must be set"); + let allowed_teams = x.split(",").collect::>(); + for team in &allowed_teams { + gh.get_team_members(&mut admins, &mut members, &format!("{}.toml", team)) + .await; + } + members.sort(); + log::debug!( + "Allowed teams: {:?}, members loaded {:?}", + &allowed_teams, + members + ); + if !members.iter().any(|m| m == &user.login) { + return Ok(Response::builder() + .status(StatusCode::UNAUTHORIZED) + .body(Body::from( + ERR_AUTH_UNAUTHORIZED.replace("{tinfra_zulip_link}", TINFRA_ZULIP_LINK), + )) + .unwrap()); + } + + let mut info_msg: &str = ""; + if req.method == hyper::Method::POST { + let mut c = body_stream; + let mut payload = Vec::new(); + while let Some(chunk) = c.next().await { + let chunk = chunk?; + payload.extend_from_slice(&chunk); + } + let prefs = url::form_urlencoded::parse(payload.as_ref()) + .into_owned() + .collect::>() + .into(); + + // TODO: maybe add more input validation + validate_data(&prefs).unwrap(); + + // save changes + set_prefs(&db_client, prefs).await.unwrap(); + + info_msg = "Preferences saved"; + } + + // Query and return all team member prefs + let mut review_capacity = get_prefs(&db_client, &mut members, &user.login, is_admin).await; + let curr_user_prefs = serde_json::json!(&review_capacity.swap_remove(0)); + let team_prefs = serde_json::json!(&review_capacity); + // log::debug!("My prefs: {:?}", curr_user_prefs); + // log::debug!("Other team prefs: {:?}", team_prefs); + + let mut context = tera::Context::new(); + context.insert("user_prefs", &curr_user_prefs); + context.insert("team_prefs", &team_prefs); + context.insert("message", &info_msg); + let body = TEMPLATES + .render("pr-prefs-backoffice.html", &context) + .unwrap(); + + let status_code = if req.method == hyper::Method::POST { + StatusCode::CREATED + } else { + StatusCode::OK + }; + let cookie_exp = Utc::now() + Duration::hours(1); + let cookie_content = format!( + "triagebot.session={}; Expires={}; Secure; HttpOnly; SameSite=Strict", + create_cookie_content(&user.login, user.id.unwrap()).to_string(), + // RFC 5322: Thu, 31 Dec 2023 23:00:00 GMT + cookie_exp.format("%a, %d %b %Y %H:%M:%S %Z") + ); + + // compress the response + let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); + encoder.write_all(body.as_bytes()).unwrap(); + let body_gzipped = encoder.finish().unwrap(); + let resp = Response::builder() + .header(hyper::header::CONTENT_TYPE, "text/html") + .header(hyper::header::CONTENT_ENCODING, "gzip") + .header( + hyper::header::SET_COOKIE, + header::HeaderValue::from_str(&cookie_content).unwrap(), + ) + .status(status_code) + .body(Body::from(body_gzipped)); + + return Ok(resp.unwrap()); + } if req.uri.path() != "/github-hook" { return Ok(Response::builder() .status(StatusCode::NOT_FOUND) @@ -233,6 +494,15 @@ async fn serve_req( } } +/// iss=triagebot, sub=gh username, uid=gh user_id, exp=now+30', checksum=sha256(user data) +fn create_cookie_content(user_login: &str, user_id: i64) -> serde_json::Value { + let auth_secret = std::env::var("BACKOFFICE_SECRET").expect("BACKOFFICE_SECRET is not set"); + let exp = Utc::now() + Duration::minutes(30); + let digest = format!("{};{};{}", user_id, user_login, auth_secret); + let digest = hex_digest(Algorithm::SHA256, &digest.into_bytes()); + serde_json::json!({"iss":"triagebot", "sub":user_login, "uid": user_id, "exp":exp, "checksum":digest}) +} + async fn run_server(addr: SocketAddr) -> anyhow::Result<()> { let pool = db::ClientPool::new(); db::run_migrations(&*pool.get().await) diff --git a/src/triage.rs b/src/triage.rs index c62bd5d8..5046b35a 100644 --- a/src/triage.rs +++ b/src/triage.rs @@ -3,7 +3,8 @@ use chrono::{Duration, Utc}; use hyper::{Body, Response, StatusCode}; use serde::Serialize; use serde_json::value::{to_value, Value}; -use std::sync::Arc; +use std::{path::Path, sync::Arc}; +use tokio::io::AsyncReadExt; use url::Url; const YELLOW_DAYS: i64 = 7; @@ -17,6 +18,34 @@ pub fn index() -> Result, hyper::Error> { .unwrap()) } +pub async fn asset(file: &str) -> Result, hyper::Error> { + let s = format!("static/{}", file); + let ext = Path::new(&s).extension().unwrap().to_str().unwrap(); + if let Ok(mut fd) = tokio::fs::File::open(&s).await { + let mut contents = String::new(); + fd.read_to_string(&mut contents) + .await + .unwrap_or_else(|err| panic!("Error loading file {}: {}", s, err)); + contents = contents.trim_end().to_string(); + let body = Body::from(contents); + let _ty = match ext { + "css" => "text/css", + "js" => "text/javascript", + "svg" => "image/svg+xml", + _ => "text/plain", + }; + Ok(Response::builder() + .header("Content-Type", _ty) + .body(body) + .unwrap()) + } else { + Ok(Response::builder() + .status(StatusCode::NOT_FOUND) + .body(Body::empty()) + .unwrap()) + } +} + pub async fn pulls( ctx: Arc, owner: &str, diff --git a/static/git-dark.svg b/static/git-dark.svg new file mode 100644 index 00000000..cb1a374e --- /dev/null +++ b/static/git-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/git-light.svg b/static/git-light.svg new file mode 100644 index 00000000..6b6a26a2 --- /dev/null +++ b/static/git-light.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/pr-prefs-backoffice.html b/templates/pr-prefs-backoffice.html new file mode 100644 index 00000000..84fc3d22 --- /dev/null +++ b/templates/pr-prefs-backoffice.html @@ -0,0 +1,193 @@ + + + + + Review settings backoffice + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% for pref in team_prefs %} + + + + + + + + + {% endfor %} + + +

{{ message }}

+

Rust project review capacity

+

A backoffice to set your own review capacity.
These settings will be used by the Triagebot when assigning reviews.

+

My prefs

Team memberActive
[?Uncheck this if you want to set yourself off-duty without a timeframe.
]
Max # assigned PRsTime offPlease notify me after
[?Days that you allow before being pinged for a review assigned to you.
]
Publish my preferences
[?Check this to have your settings publicly visible.

Note: admins will always see all members settings
]
+ + {{ user_prefs.username }} + + + {{ user_prefs.num_assigned_prs }} / + +   + +

My team prefs (read-only)

{{ pref.username }}{{ pref.num_assigned_prs }} / +   + +
+
+ + + From f7e3e97cb114e65047e24813f861642abe726add Mon Sep 17 00:00:00 2001 From: apiraino Date: Tue, 12 Sep 2023 15:38:06 +0200 Subject: [PATCH 02/10] PR preferences backoffice impl --- src/config.rs | 8 +++ src/db.rs | 15 +++++ src/handlers.rs | 4 ++ src/handlers/assign.rs | 144 ++++++++++++++++++++++++++++++++++------- 4 files changed, 148 insertions(+), 23 deletions(-) diff --git a/src/config.rs b/src/config.rs index 38e81fce..0e596b36 100644 --- a/src/config.rs +++ b/src/config.rs @@ -34,6 +34,7 @@ pub(crate) struct Config { pub(crate) note: Option, pub(crate) mentions: Option, pub(crate) no_merges: Option, + pub(crate) review_prefs: Option, } #[derive(PartialEq, Eq, Debug, serde::Deserialize)] @@ -151,6 +152,12 @@ pub(crate) struct ShortcutConfig { _empty: (), } +#[derive(PartialEq, Eq, Debug, serde::Deserialize)] +pub(crate) struct ReviewPrefsConfig { + #[serde(default)] + _empty: (), +} + #[derive(PartialEq, Eq, Debug, serde::Deserialize)] pub(crate) struct PrioritizeConfig { pub(crate) label: String, @@ -423,6 +430,7 @@ mod tests { review_submitted: None, mentions: None, no_merges: None, + review_prefs: None } ); } diff --git a/src/db.rs b/src/db.rs index a696be99..8eface34 100644 --- a/src/db.rs +++ b/src/db.rs @@ -272,5 +272,20 @@ CREATE UNIQUE INDEX jobs_name_scheduled_at_unique_index ON jobs ( name, scheduled_at ); +", + " +CREATE table review_capacity ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id BIGINT REFERENCES users(user_id), + checksum TEXT NOT NULL, + assigned_prs INT[] NOT NULL, + num_assigned_prs INTEGER, + max_assigned_prs INTEGER, + pto_date_start date, + pto_date_end date, + active boolean default true, + allow_ping_after_days INTEGER, + publish_prefs boolean NOT NULL DEFAULT false +); ", ]; diff --git a/src/handlers.rs b/src/handlers.rs index da39943b..4bc23ff2 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -41,6 +41,7 @@ mod notify_zulip; mod ping; mod prioritize; mod relabel; +pub mod review_prefs; mod review_submitted; mod rfc_helper; pub mod rustc_commits; @@ -157,6 +158,8 @@ macro_rules! issue_handlers { // // This is for events that happen only on issues (e.g. label changes). // Each module in the list must contain the functions `parse_input` and `handle_input`. +// - `parse_input` should parse and validate the input, return an object with everything needed to perform an action +// - `handle_input`: performs the action (optionally) using the input object received issue_handlers! { assign, autolabel, @@ -164,6 +167,7 @@ issue_handlers! { mentions, no_merges, notify_zulip, + review_prefs, } macro_rules! command_handlers { diff --git a/src/handlers/assign.rs b/src/handlers/assign.rs index 4a7d7f1d..9452deb7 100644 --- a/src/handlers/assign.rs +++ b/src/handlers/assign.rs @@ -20,16 +20,18 @@ use crate::{ config::AssignConfig, github::{self, Event, Issue, IssuesAction, Selection}, + handlers::review_prefs::{get_review_candidate_by_capacity, get_review_candidates_by_username}, handlers::{Context, GithubClient, IssuesEvent}, interactions::EditIssueBody, + ReviewCapacityUser, }; use anyhow::{bail, Context as _}; use parser::command::assign::AssignCommand; use parser::command::{Command, Input}; -use rand::seq::IteratorRandom; use rust_team_data::v1::Teams; use std::collections::{HashMap, HashSet}; use std::fmt; +use tokio_postgres::Client as DbClient; use tracing as log; #[cfg(test)] @@ -274,14 +276,16 @@ async fn determine_assignee( config: &AssignConfig, input: &AssignInput, ) -> anyhow::Result<(Option, bool)> { + let db_client = ctx.db.get().await; + let teams = crate::team_data::teams(&ctx.github).await?; if let Some(name) = find_assign_command(ctx, event) { if is_self_assign(&name, &event.issue.user.login) { return Ok((Some(name.to_string()), true)); } // User included `r?` in the opening PR body. - match find_reviewer_from_names(&teams, config, &event.issue, &[name]) { - Ok(assignee) => return Ok((Some(assignee), true)), + match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &[name]).await { + Ok(assignee) => return Ok((Some(assignee.username), true)), Err(e) => { event .issue @@ -294,8 +298,10 @@ async fn determine_assignee( // Errors fall-through to try fallback group. match find_reviewers_from_diff(config, &input.git_diff) { Ok(candidates) if !candidates.is_empty() => { - match find_reviewer_from_names(&teams, config, &event.issue, &candidates) { - Ok(assignee) => return Ok((Some(assignee), false)), + match find_reviewer_from_names(&db_client, &teams, config, &event.issue, &candidates) + .await + { + Ok(assignee) => return Ok((Some(assignee.username), false)), Err(FindReviewerError::TeamNotFound(team)) => log::warn!( "team {team} not found via diff from PR {}, \ is there maybe a misconfigured group?", @@ -322,8 +328,8 @@ async fn determine_assignee( } if let Some(fallback) = config.adhoc_groups.get("fallback") { - match find_reviewer_from_names(&teams, config, &event.issue, fallback) { - Ok(assignee) => return Ok((Some(assignee), false)), + match find_reviewer_from_names(&db_client, &teams, config, &event.issue, fallback).await { + Ok(assignee) => return Ok((Some(assignee.username), false)), Err(e) => { log::trace!( "failed to select from fallback group for PR {}: {e}", @@ -436,6 +442,7 @@ pub(super) async fn handle_command( } let issue = event.issue().unwrap(); + let db_client = ctx.db.get().await; if issue.is_pr() { if !issue.is_open() { issue @@ -487,8 +494,9 @@ pub(super) async fn handle_command( name.to_string() } else { let teams = crate::team_data::teams(&ctx.github).await?; - match find_reviewer_from_names(&teams, config, issue, &[name]) { - Ok(assignee) => assignee, + match find_reviewer_from_names(&db_client, &teams, config, issue, &[name]).await + { + Ok(assignee) => assignee.username, Err(e) => { issue.post_comment(&ctx.github, &e.to_string()).await?; return Ok(()); @@ -497,7 +505,11 @@ pub(super) async fn handle_command( } } }; + // NOTE: this will not handle PR assignment requested from the web Github UI + // that case is handled in the review_prefs module set_assignee(issue, &ctx.github, &username).await; + // This PR will be registered in the reviewer's work queue using a `IssuesAction::Assigned` + // and its delegate `handlers::review_prefs::handle_input()` return Ok(()); } @@ -634,19 +646,7 @@ impl fmt::Display for FindReviewerError { } } -/// Finds a reviewer to assign to a PR. -/// -/// The `names` is a list of candidate reviewers `r?`, such as `compiler` or -/// `@octocat`, or names from the owners map. It can contain GitHub usernames, -/// auto-assign groups, or rust-lang team names. It must have at least one -/// entry. -fn find_reviewer_from_names( - teams: &Teams, - config: &AssignConfig, - issue: &Issue, - names: &[String], -) -> Result { - let candidates = candidate_reviewers_from_names(teams, config, issue, names)?; +fn old_find_reviewer_from_names(candidates: HashSet<&str>) -> Result { // This uses a relatively primitive random choice algorithm. // GitHub's CODEOWNERS supports much more sophisticated options, such as: // @@ -666,6 +666,7 @@ fn find_reviewer_from_names( // // These are all ideas for improving the selection here. However, I'm not // sure they are really worth the effort. + use rand::prelude::IteratorRandom; Ok(candidates .into_iter() .choose(&mut rand::thread_rng()) @@ -673,7 +674,104 @@ fn find_reviewer_from_names( .to_string()) } -/// Returns a list of candidate usernames to choose as a reviewer. +async fn new_find_reviewer_from_names( + db_client: &DbClient, + candidates: &HashSet<&str>, +) -> Result { + let candidates_vec = candidates + .iter() + .map(|x| x.to_string()) + .collect::>(); + let assignee = if candidates_vec.len() > 1 { + // Select the best candidate from the pool + match get_review_candidate_by_capacity(&db_client, candidates_vec.clone()).await { + Ok(reviewers) => reviewers, + Err(_) => { + return Err(FindReviewerError::NoReviewer { + initial: candidates_vec, + }); + } + } + } else { + // Get the prefs of the only candidate identified + match get_review_candidates_by_username(&db_client, candidates_vec).await { + Ok(mut reviewers) => { + if reviewers.is_empty() { + return Err(FindReviewerError::NoReviewer { initial: vec![] }); + } + reviewers + .pop() + .expect("Something wrong happened while getting the reviewer") + } + Err(_) => { + return Err(FindReviewerError::NoReviewer { initial: vec![] }); + } + } + }; + Ok(assignee) +} + +/// Finds a reviewer to assign to a PR. +/// Accounts for reviewer's capacity preferences. +/// If just one candidate is available (or a specific reviewer is invoked), return that one. +/// +/// The `names` is a list of candidate reviewers `r?`, such as `compiler` or +/// `@octocat`, or names from the owners map. It can contain GitHub usernames, +/// auto-assign groups, or rust-lang team names. It must have at least one +/// entry. +async fn find_reviewer_from_names( + db_client: &DbClient, + teams: &Teams, + config: &AssignConfig, + issue: &Issue, + names: &[String], +) -> Result { + let candidates = candidate_reviewers_from_names(teams, config, issue, names)?; + let assignee: Result; + if use_new_pr_assignment(teams, &candidates) { + log::debug!("Using NEW pull request assignment workflow"); + assignee = new_find_reviewer_from_names(db_client, &candidates).await; + } else { + log::debug!("Using OLD pull request assignment workflow"); + let username = old_find_reviewer_from_names(candidates); + assignee = Ok(ReviewCapacityUser::phony(username.unwrap())); + } + assignee +} + +/// Decide whether to use the new PR assignment workflow. Returns true if: +/// - env var USE_NEW_PR_ASSIGNMENT is set +/// - candidates belong to at least 1 whitelisted team in env var NEW_PR_ASSIGNMENT_TEAMS i.e. teams selected as +/// testers cohort +fn use_new_pr_assignment(teams: &Teams, candidates: &HashSet<&str>) -> bool { + // this filter will return if a candidate is a member of a team + let mut filter = |team_member_name: &&str| -> bool { + candidates + .iter() + .any(|candidate_name| candidate_name == team_member_name) + }; + + if std::env::var("USE_NEW_PR_ASSIGNMENT").is_ok() { + let x = std::env::var("NEW_PR_ASSIGNMENT_TEAMS") + .expect("NEW_PR_ASSIGNMENT_TEAMS env var must be set"); + let allowed_teams = x.split(",").collect::>(); + for t in allowed_teams { + let team = teams.teams.get(t).unwrap(); + // find the first candidate that belongs to an allowed team + let candidates_that_are_members = team + .members + .iter() + .map(|member| member.github.as_str()) + .filter(&mut filter) + .collect::>(); + if !candidates_that_are_members.is_empty() { + return true; + } + } + } + false +} + fn candidate_reviewers_from_names<'a>( teams: &'a Teams, config: &'a AssignConfig, From 4059c97f076e1358343f6a4f4820843855baf569 Mon Sep 17 00:00:00 2001 From: apiraino Date: Tue, 12 Sep 2023 15:38:54 +0200 Subject: [PATCH 03/10] Avoid triggering spurious triagebot command --- src/handlers/assign.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/handlers/assign.rs b/src/handlers/assign.rs index 9452deb7..a2830b96 100644 --- a/src/handlers/assign.rs +++ b/src/handlers/assign.rs @@ -61,7 +61,7 @@ const RETURNING_USER_WELCOME_MESSAGE: &str = "r? @{assignee} (rustbot has picked a reviewer for you, use r? to override)"; const RETURNING_USER_WELCOME_MESSAGE_NO_REVIEWER: &str = - "@{author}: no appropriate reviewer found, use r? to override"; + "@{author}: no appropriate reviewer found, use `r?` to override"; const ON_VACATION_WARNING: &str = "{username} is on vacation. Please do not assign them to PRs."; @@ -627,7 +627,7 @@ impl fmt::Display for FindReviewerError { f, "No reviewers could be found from initial request `{}`\n\ This repo may be misconfigured.\n\ - Use r? to specify someone else to assign.", + Use `r?` to specify someone else to assign.", initial.join(",") ) } @@ -637,7 +637,7 @@ impl fmt::Display for FindReviewerError { "Could not assign reviewer from: `{}`.\n\ User(s) `{}` are either the PR author, already assigned, or on vacation, \ and there are no other candidates.\n\ - Use r? to specify someone else to assign.", + Use `r?` to specify someone else to assign.", initial.join(","), filtered.join(","), ) From 30dceeafb6451a2d3b4f6a86c9c4554b91bc0463 Mon Sep 17 00:00:00 2001 From: apiraino Date: Tue, 12 Sep 2023 15:39:31 +0200 Subject: [PATCH 04/10] Update dependencies and documentation --- .env.sample | 27 +++++++++++++ Cargo.lock | 90 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 4 +- README.md | 6 +++ pr_prefs_backoffice.md | 39 ++++++++++++++++++ 5 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 pr_prefs_backoffice.md diff --git a/.env.sample b/.env.sample index 405d97dd..6bbc1c95 100644 --- a/.env.sample +++ b/.env.sample @@ -5,3 +5,30 @@ GITHUB_WEBHOOK_SECRET=MUST_BE_CONFIGURED # for logging, refer to this document: https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html # `RUSTC_LOG` is not required to run the application, but it makes local development easier # RUST_LOG=MUST_BE_CONFIGURED + +# Your local triagebot will be reachable from the internet using this URL +# This is useful if you configure a webhook from GitHub and want to use your local triagebot as receiving end of the GitHub webhooks. +TRIAGEBOT_HOST=http://7e9ea9dc.ngrok-free.app + +# These are identifiers about your Github App for the pull request prefs backoffice (see pr_prefs_backoffice.md). Needed only if +# want to hack locally on the backoffice. +# CLIENT_ID is public +# CLIENT_SECRET must be kept, well, secret. +CLIENT_ID="xxx" +CLIENT_SECRET="yyy" + +# Flag to enable the new pull request assignment workflow (see pr_prefs_backoffice.md). +# If this env var is UNSET, the old pull request assignment is used (basically random assignment). +# If this env var is SET, the new pull request assignment reads the Rust contributors preferences and assigns PRs accordingly. +USE_NEW_PR_ASSIGNMENT=yay + +# A comma separated list of teams that are allowed to use the new PR assignment workflow. +# Used to limit the number of users during the test phase. +# Team name matches names in the rust-lang/team repository: +# https://github.com/rust-lang/team/tree/master/teams +NEW_PR_ASSIGNMENT_TEAMS=compiler,compiler-contributors + +# This is a secret used to create a checksum of the login cookie when accessing +# the pull request preferences backoffice. Only the server knows this secret. +# Could be generated with something like: openssl rand -hex 20 +BACKOFFICE_SECRET=xxx diff --git a/Cargo.lock b/Cargo.lock index 969c430c..92ed90c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,7 +80,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.4.4", "object", "rustc-demangle", ] @@ -186,6 +186,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "combine" version = "3.8.1" @@ -199,6 +208,24 @@ dependencies = [ "unreachable", ] +[[package]] +name = "commoncrypto" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007" +dependencies = [ + "commoncrypto-sys", +] + +[[package]] +name = "commoncrypto-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2" +dependencies = [ + "libc", +] + [[package]] name = "comrak" version = "0.8.2" @@ -251,6 +278,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "cron" version = "0.12.0" @@ -282,6 +318,18 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-hash" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a77162240fd97248d19a564a565eb563a3f592b386e4136fb300909e67dddca" +dependencies = [ + "commoncrypto", + "hex 0.3.2", + "openssl", + "winapi", +] + [[package]] name = "cynic" version = "2.2.2" @@ -449,6 +497,17 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" +dependencies = [ + "crc32fast", + "libz-ng-sys", + "miniz_oxide 0.7.1", +] + [[package]] name = "fnv" version = "1.0.7" @@ -696,6 +755,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" + [[package]] name = "hex" version = "0.4.3" @@ -901,6 +966,16 @@ version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb691a747a7ab48abc15c5b42066eaafde10dc427e3b6ee2a1cf43db04c763bd" +[[package]] +name = "libz-ng-sys" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dd9f43e75536a46ee0f92b758f6b63846e594e86638c61a9251338a65baea63" +dependencies = [ + "cmake", + "libc", +] + [[package]] name = "lock_api" version = "0.4.7" @@ -978,6 +1053,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.2" @@ -2035,12 +2119,14 @@ dependencies = [ "chrono", "comrak", "cron", + "crypto-hash", "cynic", "dotenv", + "flate2", "futures", "github-graphql", "glob", - "hex", + "hex 0.4.3", "hyper", "ignore", "itertools", diff --git a/Cargo.toml b/Cargo.toml index da8a733d..43b51f71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ rust_team_data = { git = "https://github.com/rust-lang/team" } glob = "0.3.0" toml = "0.5.1" hyper = { version = "0.14.4", features = ["server", "stream"]} -tokio = { version = "1.7.1", features = ["macros", "time", "rt"] } +tokio = { version = "1.7.1", features = ["fs", "macros", "time", "rt"] } futures = { version = "0.3", default-features = false, features = ["std"] } async-trait = "0.1.31" uuid = { version = "0.8", features = ["v4", "serde"] } @@ -45,6 +45,8 @@ ignore = "0.4.18" postgres-types = { version = "0.2.4", features = ["derive"] } cron = { version = "0.12.0" } bytes = "1.1.0" +crypto-hash = { version = "0.3.4", default-features = false } +flate2 = { version = "1.0.27", default-features = false, features = ["zlib-ng"] } [dependencies.serde] version = "1" diff --git a/README.md b/README.md index 85216d9a..f8451723 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,12 @@ The general overview of what you will need to do: 6. Add a `triagebot.toml` file to the main branch of your GitHub repo with whichever services you want to try out. 7. Try interacting with your repo, such as issuing `@rustbot` commands or interacting with PRs and issues (depending on which services you enabled in `triagebot.toml`). Watch the logs from the server to see what's going on. +## The pull requests assignment preferences backoffice + +This is an administrative backoffice targeted at the Rust project contributors to set their preferences for pull request assignment. + +Read all the documentation [here](./pr_prefs_backoffice.md). + ### Configure a database To use Postgres, you will need to install it and configure it: diff --git a/pr_prefs_backoffice.md b/pr_prefs_backoffice.md new file mode 100644 index 00000000..c4271dd2 --- /dev/null +++ b/pr_prefs_backoffice.md @@ -0,0 +1,39 @@ +# Pull request assignment preferences backoffice + +This is an administrative backoffice targeted at the Rust project contributors to set their preferences for pull request assignment. + +When assigning the review of pull requests, this backoffice allows contributors to: +- set themselves on leave for any amount of time. During this time off contributors won't be assigned any new pull request +- set the maximum number of pull requests assigned to them +- set the desired number of days before a pull request assigned for review to the contributor might be considered for a reminder +- allow a flag to make their own preferences visible to all team members or only to team leaders and system administrators + +This is a mostly static web page server-side generated, using the least amount possible of JavaScript. + +This backoffice will set one cookie (`triagebot.session`) to understand if a user is already logged in. The cookie expires after +1 hour and is renewed at every access. The cookie is set to `Secure=true`, `HttpOnly` and `SameSite=Strict`. + +Access authorization is handled by GitHub, so users will need to be logged in GitHub and authorize this Github Application. + +Access to this backoffice is granted only to GitHub users that are members of a Rust project team (teams are defined [here](https://github.com/rust-lang/team/tree/HEAD/teams)). Only specific Rust teams are allowed to use +this backoffice (mostly for testing purposes, to switch users to the new workflow slowly). Teams allowed are defined in the env var `NEW_PR_ASSIGNMENT_TEAMS` (see `.env.sample`). + +Teams members are expected to set their own review preferences using this backoffice. In case a team member didn't yet set their own preferences, these defaults will be applied: +- Max 5 pull requests assigned (see constant `PREF_MAX_ASSIGNED_PRS`) +- 20 days before a notification is sent for a pull request assigned that is waiting for review (see constant `PREF_ALLOW_PING_AFTER_DAYS`) + +## How to locally run this backoffice + +- Configure a webhook pointing to a local instance of the triagebot. Follow the instructions [in the README](https://github.com/rust-lang/triagebot#configure-webhook-forwarding). +- Configure a repository under your GitHub username and configure the same webhook URL in the "Webhooks" settings of the repository. +- Create a GiHub Application and configure the callback URL ([here](https://github.com/settings/apps)) pointing to your proxied triagebot backoffice using the path to the backoffice (ex. `http://7e9ea9dc.ngrok.io/github-hook/review-settings`) +- Start your local triagebot: load the environment variable from a file (make a copy of `.env.sample`) and run `RUST_LOG=DEBUG cargo run --bin triagebot` + +## TODO + +- [ ] Handle cleanup of the preferences DB table for team members not existing anymore in the teams .toml: delete their assignments, PRs go back to the pool of those needing an assignee +- [ ] Cache somehow teams .toml download from github to avoid retrieving those `.toml` files too often +- [ ] maybe more input validation, see `validate_data()` in `./src/main.rs` +- [ ] Now we are handling contributors workload for a single team. Some contributors work across teams. Make this backoffice aware of other teams and show the actual workload of contributors + + From 8087779fd66be25f0e30cef8cbf423dd90940d51 Mon Sep 17 00:00:00 2001 From: apiraino Date: Tue, 12 Sep 2023 15:40:09 +0200 Subject: [PATCH 05/10] Add PR backoffice logic --- src/handlers/review_prefs.rs | 304 +++++++++++++++++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 src/handlers/review_prefs.rs diff --git a/src/handlers/review_prefs.rs b/src/handlers/review_prefs.rs new file mode 100644 index 00000000..26b451b2 --- /dev/null +++ b/src/handlers/review_prefs.rs @@ -0,0 +1,304 @@ +use crate::{ + config::ReviewPrefsConfig, + github::{IssuesAction, IssuesEvent, Selection}, + handlers::Context, + ReviewCapacityUser, +}; +use anyhow::Context as _; +use tokio_postgres::Client as DbClient; +use tracing as log; + +// This module updates the PR work queue of reviewers +// - Adds the PR to the work queue of the user (when the PR has been assigned) +// - Removes the PR from the work queue of the user (when the PR is unassigned or closed) +// - Rollbacks the PR assignment in case the specific user ("r? user") is inactive/not available + +/// Set review capacity for a team member +pub async fn set_prefs( + db: &DbClient, + prefs: ReviewCapacityUser, +) -> anyhow::Result { + let q = " +UPDATE review_capacity r +SET max_assigned_prs = $2, pto_date_start = $3, pto_date_end = $4, active = $5, allow_ping_after_days = $6, publish_prefs = $7 +FROM users u +WHERE r.user_id=$1 AND u.user_id=r.user_id +RETURNING u.username, r.*"; + log::debug!("pref {:?}", prefs); + let rec = db + .query_one( + q, + &[ + &prefs.user_id, + &prefs.max_assigned_prs, + &prefs.pto_date_start, + &prefs.pto_date_end, + &prefs.active, + &prefs.allow_ping_after_days, + &prefs.publish_prefs, + ], + ) + .await + .context("Update DB error")?; + Ok(rec.into()) +} + +/// Return a team member identified by a checksum +pub async fn get_user(db_client: &DbClient, checksum: &str) -> anyhow::Result { + let q = " +SELECT username,r.* +FROM review_capacity r +JOIN users on r.user_id=users.user_id +WHERE r.checksum=$1"; + let rec = db_client + .query_one(q, &[&checksum]) + .await + .context("SQL error")?; + Ok(rec.into()) +} + +/// Get review preferences for a number of team members +/// - me: sort the current user at the top of the list +/// - is_admin: if `true` return also preferences marked as not public +pub async fn get_prefs( + db: &DbClient, + users: &mut Vec, + me: &str, + is_admin: bool, +) -> Vec { + let q = format!( + " +SELECT username,r.* +FROM review_capacity r +JOIN users on r.user_id=users.user_id +WHERE username = any($1) +ORDER BY case when username='{}' then 1 else 2 end, username;", + me + ); + + let rows = db.query(&q, &[users]).await.unwrap(); + rows.into_iter() + .filter_map(|row| { + let rec = ReviewCapacityUser::from(row); + if rec.username == me || rec.publish_prefs || (!rec.publish_prefs && is_admin) { + Some(rec) + } else { + None + } + }) + .collect() +} + +/// Get review preferences for a number of team members +pub async fn get_review_candidates_by_username( + db: &DbClient, + usernames: Vec, +) -> anyhow::Result> { + let q = format!( + " +SELECT u.username, r.* +FROM review_capacity r +JOIN users u on u.user_id=r.user_id +WHERE username = ANY('{{ {} }}')", + usernames.join(",") + ); + let rows = db.query(&q, &[]).await.unwrap(); + Ok(rows + .into_iter() + .filter_map(|row| Some(ReviewCapacityUser::from(row))) + .collect()) +} + +/// Get review preferences for a number of team members, filter by review capacity +pub async fn get_review_candidate_by_capacity( + db: &DbClient, + usernames: Vec, +) -> anyhow::Result { + let q = format!( + " +SELECT username, r.*, sum(r.max_assigned_prs - r.num_assigned_prs) as avail_slots +FROM review_capacity r +JOIN users on users.user_id=r.user_id +WHERE active = true +AND current_date NOT BETWEEN pto_date_start AND pto_date_end +AND username = ANY('{{ {} }}') +AND num_assigned_prs < max_assigned_prs +GROUP BY username, r.id +ORDER BY avail_slots DESC +LIMIT 1", + usernames.join(",") + ); + let rec = db.query_one(&q, &[]).await.context("Select DB error")?; + Ok(rec.into()) +} + +/// Get all assignees of a pull request +async fn get_review_pr_assignees( + db: &DbClient, + issue_num: u64, +) -> anyhow::Result> { + let q = format!( + " +SELECT u.username, r.* +FROM review_capacity r +JOIN users u on u.user_id=r.user_id +WHERE {} = ANY (assigned_prs);", + issue_num, + ); + + let rows = db.query(&q, &[]).await.unwrap(); + Ok(rows + .into_iter() + .filter_map(|row| Some(ReviewCapacityUser::from(row))) + .collect()) +} + +pub(super) struct ReviewPrefsInput {} + +pub(super) async fn parse_input( + _ctx: &Context, + event: &IssuesEvent, + config: Option<&ReviewPrefsConfig>, +) -> Result, String> { + log::debug!("[review_prefs] parse_input"); + let _config = match config { + Some(config) => config, + None => return Ok(None), + }; + + log::debug!( + "[review_prefs] now matching the action for event {:?}", + event + ); + match event.action { + IssuesAction::Assigned => { + log::debug!("[review_prefs] IssuesAction::Assigned: Will add to work queue"); + Ok(Some(ReviewPrefsInput {})) + } + IssuesAction::Unassigned | IssuesAction::Closed => { + log::debug!("[review_prefs] IssuesAction::Unassigned | IssuesAction::Closed: Will remove from work queue"); + Ok(Some(ReviewPrefsInput {})) + } + _ => { + log::debug!("[review_prefs] Other action on PR {:?}", event.action); + Ok(None) + } + } +} + +async fn update_assigned_prs( + db: &DbClient, + user_id: i64, + assigned_prs: &Vec, +) -> anyhow::Result { + let q = " +UPDATE review_capacity r +SET assigned_prs = $2, num_assigned_prs = $3 +FROM users u +WHERE r.user_id=$1 AND u.user_id=r.user_id +RETURNING u.username, r.*"; + let num_assigned_prs = assigned_prs.len() as i32; + let rec = db + .query_one(q, &[&user_id, assigned_prs, &num_assigned_prs]) + .await + .context("Update DB error")?; + Ok(rec.into()) +} + +pub(super) async fn handle_input<'a>( + ctx: &Context, + _config: &ReviewPrefsConfig, + event: &IssuesEvent, + _inputs: ReviewPrefsInput, +) -> anyhow::Result<()> { + log::debug!("[review_prefs] handle_input"); + let db_client = ctx.db.get().await; + + // Note: + // When assigning or unassigning a PR, we don't receive the assignee(s) removed from the PR + // so we need to run two queries: + + // 1) unassign this PR from everyone + let current_assignees = get_review_pr_assignees(&db_client, event.issue.number) + .await + .unwrap(); + for mut rec in current_assignees { + if let Some(index) = rec + .assigned_prs + .iter() + .position(|value| *value == event.issue.number as i32) + { + rec.assigned_prs.swap_remove(index); + } + update_assigned_prs(&db_client, rec.user_id, &rec.assigned_prs) + .await + .unwrap(); + } + + // If the action is to unassign/close a PR, nothing else to do + if event.action == IssuesAction::Closed || event.action == IssuesAction::Unassigned { + return Ok(()); + } + + // 2) assign the PR to the requested team members + let usernames = event + .issue + .assignees + .iter() + .map(|x| x.login.clone()) + .collect::>(); + let requested_assignees = get_review_candidates_by_username(&db_client, usernames) + .await + .unwrap(); + // iterate the list of requested assignees and try to assign the issue to each of them + // in case of failure, emit a comment (for each failure) + for mut assignee_prefs in requested_assignees { + log::debug!( + "Trying to assign to {}, with prefs {:?}", + &assignee_prefs.username, + &assignee_prefs + ); + + // If Github just assigned a PR to an inactive/unavailable user + // publish a comment notifying the error and rollback the PR assignment + if event.action == IssuesAction::Assigned + && (!assignee_prefs.active || !assignee_prefs.is_available()) + { + log::debug!( + "PR assigned to {}, which is not available: will revert this.", + &assignee_prefs.username + ); + event + .issue + .post_comment( + &ctx.github, + &format!( + "Could not assign PR to user {}, please reroll for the team", + &assignee_prefs.username + ), + ) + .await + .context("Failed posting a comment on Github")?; + event + .issue + .remove_assignees(&ctx.github, Selection::One(&assignee_prefs.username)) + .await + .context("Failed unassigning the PR")?; + } + + let iss_num = event.issue.number as i32; + if !assignee_prefs.assigned_prs.contains(&iss_num) { + assignee_prefs.assigned_prs.push(iss_num) + } + + update_assigned_prs( + &db_client, + assignee_prefs.user_id, + &assignee_prefs.assigned_prs, + ) + .await + .unwrap(); + } + + Ok(()) +} From 6a13ed6eb35e86467ed05126d61f765a9fff7f15 Mon Sep 17 00:00:00 2001 From: apiraino Date: Thu, 14 Sep 2023 10:41:19 +0200 Subject: [PATCH 06/10] Add more TODO items --- pr_prefs_backoffice.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pr_prefs_backoffice.md b/pr_prefs_backoffice.md index c4271dd2..46ace568 100644 --- a/pr_prefs_backoffice.md +++ b/pr_prefs_backoffice.md @@ -31,6 +31,8 @@ Teams members are expected to set their own review preferences using this backof ## TODO +- [ ] Build some tooling to import members from a team `.toml` file (SQL fixtures, CLI, or else) +- [ ] Set some defaults in the DB table structure for contributors that did not set any for themselves. - [ ] Handle cleanup of the preferences DB table for team members not existing anymore in the teams .toml: delete their assignments, PRs go back to the pool of those needing an assignee - [ ] Cache somehow teams .toml download from github to avoid retrieving those `.toml` files too often - [ ] maybe more input validation, see `validate_data()` in `./src/main.rs` From 48c4f26cda0f72fa6d9ec70554e744e8027552a3 Mon Sep 17 00:00:00 2001 From: apiraino Date: Thu, 28 Sep 2023 21:11:38 +0200 Subject: [PATCH 07/10] Remove redundant teams members download --- src/db.rs | 14 ++++++------- src/db/notifications.rs | 2 +- src/github.rs | 29 ++++++++++++++------------ src/handlers/assign.rs | 2 +- src/handlers/notification.rs | 2 +- src/handlers/review_prefs.rs | 34 ++++++++++++++++++++++++++---- src/lib.rs | 38 ++++++++++++---------------------- src/main.rs | 40 ++++++++++++++++++++++-------------- 8 files changed, 94 insertions(+), 67 deletions(-) diff --git a/src/db.rs b/src/db.rs index 8eface34..8045275f 100644 --- a/src/db.rs +++ b/src/db.rs @@ -92,7 +92,7 @@ impl ClientPool { } } -async fn make_client() -> anyhow::Result { +pub async fn make_client() -> anyhow::Result { let db_url = std::env::var("DATABASE_URL").expect("needs DATABASE_URL"); if db_url.contains("rds.amazonaws.com") { let cert = &CERTIFICATE_PEM[..]; @@ -277,15 +277,15 @@ CREATE UNIQUE INDEX jobs_name_scheduled_at_unique_index CREATE table review_capacity ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, user_id BIGINT REFERENCES users(user_id), - checksum TEXT NOT NULL, - assigned_prs INT[] NOT NULL, + active boolean NOT NULL DEFAULT true, + assigned_prs INT[] NOT NULL DEFAULT array[]::INT[], + max_assigned_prs INTEGER NOT NULL DEFAULT 5, num_assigned_prs INTEGER, - max_assigned_prs INTEGER, pto_date_start date, pto_date_end date, - active boolean default true, - allow_ping_after_days INTEGER, - publish_prefs boolean NOT NULL DEFAULT false + allow_ping_after_days INTEGER NOT NULL DEFAULT 15, + publish_prefs boolean NOT NULL DEFAULT false, + checksum TEXT ); ", ]; diff --git a/src/db/notifications.rs b/src/db/notifications.rs index 5b185793..776853e5 100644 --- a/src/db/notifications.rs +++ b/src/db/notifications.rs @@ -15,7 +15,7 @@ pub struct Notification { pub team_name: Option, } -pub async fn record_username(db: &DbClient, user_id: i64, username: String) -> anyhow::Result<()> { +pub async fn record_username(db: &DbClient, user_id: i64, username: &str) -> anyhow::Result<()> { db.execute( "INSERT INTO users (user_id, username) VALUES ($1, $2) ON CONFLICT DO NOTHING", &[&user_id, &username], diff --git a/src/github.rs b/src/github.rs index 43a64f02..53584b9e 100644 --- a/src/github.rs +++ b/src/github.rs @@ -7,6 +7,7 @@ use hyper::header::HeaderValue; use once_cell::sync::OnceCell; use reqwest::header::{AUTHORIZATION, USER_AGENT}; use reqwest::{Client, Request, RequestBuilder, Response, StatusCode}; +use rust_team_data::v1::TeamMember; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::{ @@ -168,21 +169,23 @@ impl GithubClient { Ok(serde_json::from_slice(&body)?) } - pub async fn get_team_members<'a>( + pub fn get_team_members<'a>( &self, - admins: &mut Vec, - members: &mut Vec, - team: &str, + admins: &mut Vec, + members: &mut Vec, + team_members: &Vec, ) { - let req = self.get(&format!( - "https://raw.githubusercontent.com/rust-lang/team/HEAD/teams/{}", - team, - )); - let (contents, _) = self.send_req(req).await.unwrap(); - let body = String::from_utf8_lossy(&contents).to_string(); - let mut config: crate::Config = toml::from_str(&body).unwrap(); - members.append(&mut config.people.members); - admins.append(&mut config.people.leads); + for tm in team_members { + let m = User { + id: Some(tm.github_id as i64), + login: tm.github.clone(), + }; + if tm.is_lead { + admins.push(m); + } else { + members.push(m); + } + } } } diff --git a/src/handlers/assign.rs b/src/handlers/assign.rs index a2830b96..1df0aac2 100644 --- a/src/handlers/assign.rs +++ b/src/handlers/assign.rs @@ -734,7 +734,7 @@ async fn find_reviewer_from_names( } else { log::debug!("Using OLD pull request assignment workflow"); let username = old_find_reviewer_from_names(candidates); - assignee = Ok(ReviewCapacityUser::phony(username.unwrap())); + assignee = Ok(ReviewCapacityUser::default(username.unwrap())); } assignee } diff --git a/src/handlers/notification.rs b/src/handlers/notification.rs index 718b2b24..09a775d1 100644 --- a/src/handlers/notification.rs +++ b/src/handlers/notification.rs @@ -100,7 +100,7 @@ pub async fn handle(ctx: &Context, event: &Event) -> anyhow::Result<()> { continue; } - if let Err(err) = notifications::record_username(&client, user.id.unwrap(), user.login) + if let Err(err) = notifications::record_username(&client, user.id.unwrap(), &user.login) .await .context("failed to record username") { diff --git a/src/handlers/review_prefs.rs b/src/handlers/review_prefs.rs index 26b451b2..04a486d8 100644 --- a/src/handlers/review_prefs.rs +++ b/src/handlers/review_prefs.rs @@ -13,10 +13,32 @@ use tracing as log; // - Removes the PR from the work queue of the user (when the PR is unassigned or closed) // - Rollbacks the PR assignment in case the specific user ("r? user") is inactive/not available -/// Set review capacity for a team member +/// Remove review preferences for these team members +pub async fn delete_prefs(db: &DbClient, user_ids: &Vec) -> anyhow::Result<()> { + db.execute( + "DELETE FROM review_capacity where user_id = any($1)", + &[&user_ids], + ) + .await + .context("Delete DB error")?; + Ok(()) +} + +/// Add stub record for a team member review capacity +pub async fn add_prefs(db: &DbClient, user_id: i64) -> anyhow::Result<()> { + db.execute( + "INSERT INTO review_capacity (user_id) VALUES ($1)", + &[&user_id], + ) + .await + .context("Insert DB error")?; + Ok(()) +} + +/// Update review capacity for a team member pub async fn set_prefs( db: &DbClient, - prefs: ReviewCapacityUser, + prefs: &ReviewCapacityUser, ) -> anyhow::Result { let q = " UPDATE review_capacity r @@ -62,7 +84,7 @@ WHERE r.checksum=$1"; /// - is_admin: if `true` return also preferences marked as not public pub async fn get_prefs( db: &DbClient, - users: &mut Vec, + users: &Vec, me: &str, is_admin: bool, ) -> Vec { @@ -76,7 +98,11 @@ ORDER BY case when username='{}' then 1 else 2 end, username;", me ); - let rows = db.query(&q, &[users]).await.unwrap(); + let rows = db + .query(&q, &[&users]) + .await + .context("Error retrieving review preferences") + .unwrap(); rows.into_iter() .filter_map(|row| { let rec = ReviewCapacityUser::from(row); diff --git a/src/lib.rs b/src/lib.rs index 58b65135..f1ade990 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ use crate::github::PullRequestDetails; use anyhow::Context; use handlers::HandlerError; use interactions::ErrorComment; -use serde::{Deserialize, Serialize}; +use serde::Serialize; use std::{collections::HashMap, fmt}; use tracing as log; @@ -26,7 +26,7 @@ pub mod notification_listing; pub mod payload; pub mod rfcbot; pub mod team; -mod team_data; +pub mod team_data; pub mod triage; pub mod zulip; @@ -263,23 +263,24 @@ pub async fn webhook( } } -const PREF_ALLOW_PING_AFTER_DAYS: i32 = 20; +// these constants should match the DB table fields defaults in ./src/db.rs +const PREF_ALLOW_PING_AFTER_DAYS: i32 = 15; const PREF_MAX_ASSIGNED_PRS: i32 = 5; #[derive(Debug, Serialize)] pub struct ReviewCapacityUser { pub username: String, pub id: uuid::Uuid, - pub checksum: String, pub user_id: i64, + pub active: bool, pub assigned_prs: Vec, - pub num_assigned_prs: Option, pub max_assigned_prs: Option, + pub num_assigned_prs: Option, pub pto_date_start: Option, pub pto_date_end: Option, - pub active: bool, pub allow_ping_after_days: Option, pub publish_prefs: bool, + pub checksum: Option, } impl ReviewCapacityUser { @@ -290,21 +291,20 @@ impl ReviewCapacityUser { self.active && is_available } - // thin compat. layer, will be removed after stabilizing the new PR assignment - fn phony(username: String) -> Self { + pub fn default(username: String) -> Self { Self { username, id: uuid::Uuid::new_v4(), user_id: -1, - checksum: String::new(), + active: true, assigned_prs: vec![], + max_assigned_prs: Some(PREF_MAX_ASSIGNED_PRS), num_assigned_prs: None, - max_assigned_prs: None, pto_date_start: None, pto_date_end: None, - active: false, - allow_ping_after_days: None, + allow_ping_after_days: Some(PREF_ALLOW_PING_AFTER_DAYS), publish_prefs: false, + checksum: None, } } } @@ -398,7 +398,7 @@ impl From> for ReviewCapacityUser { // fields we don't receive from the web form (they are here ignored) username: String::new(), id: uuid::Uuid::new_v4(), - checksum: String::new(), + checksum: None, // fields received from the web form user_id: obj.get("user_id").unwrap().parse::().unwrap(), assigned_prs, @@ -432,15 +432,3 @@ impl From for ReviewCapacityUser { } } } - -// Used to deserialize .toml from GitHub -#[derive(Deserialize)] -struct Config { - people: People, -} - -#[derive(Deserialize)] -struct People { - leads: Vec, - members: Vec, -} diff --git a/src/main.rs b/src/main.rs index d9db379f..f667efbc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -184,11 +184,7 @@ async fn serve_req( } if req.uri.path() == "/review-settings" { - let mut members = vec![]; - // yes, I am an hardcoded admin - let mut admins = vec!["apiraino".to_string()]; let gh = github::GithubClient::new_with_default_token(Client::new()); - // check if we received a session cookie let maybe_user_enc = match req.headers.get("Cookie") { Some(cookie_string) => cookie_string @@ -207,6 +203,7 @@ async fn serve_req( _ => None, }; + // Authentication: understand who this user is let db_client = ctx.db.get().await; let user = match maybe_user_enc { Some(user_enc) => { @@ -312,26 +309,29 @@ async fn serve_req( } }; - // Here we have a validated username. From now on, we will trust this user - let is_admin = admins.contains(&user.login); - log::debug!("current user={}, is admin: {}", user.login, is_admin); + let mut members: Vec = vec![]; + // I am an hardcoded admin + let mut admins = vec![User { + id: Some(6098822), + login: "apiraino".to_string(), + }]; // get team members from github (HTTP raw file retrieval, no auth used) - // TODO: maybe add some kind of caching for these files + let teams_data = triagebot::team_data::teams(&gh).await.unwrap(); let x = std::env::var("NEW_PR_ASSIGNMENT_TEAMS") .expect("NEW_PR_ASSIGNMENT_TEAMS env var must be set"); let allowed_teams = x.split(",").collect::>(); - for team in &allowed_teams { - gh.get_team_members(&mut admins, &mut members, &format!("{}.toml", team)) - .await; + for allowed_team in &allowed_teams { + let team = teams_data.teams.get(*allowed_team).unwrap(); + gh.get_team_members(&mut admins, &mut members, &team.members); } - members.sort(); + log::debug!( "Allowed teams: {:?}, members loaded {:?}", &allowed_teams, members ); - if !members.iter().any(|m| m == &user.login) { + if !members.iter().any(|m| m.login == user.login) { return Ok(Response::builder() .status(StatusCode::UNAUTHORIZED) .body(Body::from( @@ -340,6 +340,10 @@ async fn serve_req( .unwrap()); } + // Here we have a validated username. From now on, we will trust this user + let is_admin = admins.contains(&user); + log::debug!("current user={}, is admin: {}", user.login, is_admin); + let mut info_msg: &str = ""; if req.method == hyper::Method::POST { let mut c = body_stream; @@ -357,13 +361,19 @@ async fn serve_req( validate_data(&prefs).unwrap(); // save changes - set_prefs(&db_client, prefs).await.unwrap(); + set_prefs(&db_client, &prefs).await.unwrap(); info_msg = "Preferences saved"; } // Query and return all team member prefs - let mut review_capacity = get_prefs(&db_client, &mut members, &user.login, is_admin).await; + let mut review_capacity = get_prefs( + &db_client, + &members.iter().map(|tm| tm.login.clone()).collect(), + &user.login, + is_admin, + ) + .await; let curr_user_prefs = serde_json::json!(&review_capacity.swap_remove(0)); let team_prefs = serde_json::json!(&review_capacity); // log::debug!("My prefs: {:?}", curr_user_prefs); From ce8b243be949a636c7a4ad3b3851a40ed8705163 Mon Sep 17 00:00:00 2001 From: apiraino Date: Thu, 28 Sep 2023 21:12:36 +0200 Subject: [PATCH 08/10] Initial setup of review preferences This CLI tool insert or sync all members defined in $NEW_PR_ASSIGNMENT_TEAMS into the review preferences DB table. --- src/bin/import-users-from-github.rs | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 src/bin/import-users-from-github.rs diff --git a/src/bin/import-users-from-github.rs b/src/bin/import-users-from-github.rs new file mode 100644 index 00000000..d5437137 --- /dev/null +++ b/src/bin/import-users-from-github.rs @@ -0,0 +1,107 @@ +use reqwest::Client; +use tracing::{debug, info}; +use triagebot::db::notifications::record_username; +use triagebot::github::User; +use triagebot::handlers::review_prefs::{add_prefs, delete_prefs, get_prefs}; +use triagebot::{db::make_client, github}; + +// - 1. Download the teams and retrieve those listed in $NEW_PR_ASSIGNMENT_TEAMS +// - 2. match the team `people.members` with the table `users` and `review_capacity` +// - 3. Follow this scheme to perform a sync + +// | - | triagebot.users | triagebot.review_capacity | action | +// |-------|-----------------|---------------------------|---------------------------------------| +// | V | V | X | add | +// | V | X | X | add | +// | V | X | V | add | +// | X | V | X | do nothing | +// | X | X | V | remove from triagebot.review_capacity | + +#[tokio::main(flavor = "current_thread")] +async fn main() -> anyhow::Result<()> { + dotenv::dotenv().ok(); + tracing_subscriber::fmt::init(); + + let gh = github::GithubClient::new_with_default_token(Client::new()); + let db_client = make_client().await.unwrap(); + let teams_data = triagebot::team_data::teams(&gh).await?; + + // 1. get team members + let x = std::env::var("NEW_PR_ASSIGNMENT_TEAMS") + .expect("NEW_PR_ASSIGNMENT_TEAMS env var must be set"); + let allowed_teams = x.split(",").collect::>(); + info!("Will download members for teams: {:?}", allowed_teams); + for team in &allowed_teams { + let members = teams_data.teams.get(*team).unwrap(); + let team_members = members + .members + .iter() + .map(|tm| User { + login: tm.github.clone(), + id: Some(tm.github_id as i64), + }) + .collect::>(); + debug!("Team {} members loaded: {:?}", team, team_members); + + // 2. get team members review capacity + let team_review_prefs = get_prefs( + &db_client, + &team_members + .iter() + .map(|tm| tm.login.clone()) + .collect::>(), + "apiraino", + true, + ) + .await; + + // 3. Add missing team members to the review preferences table + for member in &team_members { + if !team_review_prefs + .iter() + .find(|rec| rec.username == member.login) + .is_some() + { + debug!( + "Team member {:?} was NOT found in the prefs DB table", + member + ); + + // ensure this person exists in the users DB table first + let team_member = team_members + .iter() + .find(|m| m.login == member.login) + .expect(&format!( + "Could not find member {:?} in team {}", + member, team + )); + let _ = record_username(&db_client, team_member.id.unwrap() as i64, &member.login) + .await; + + // Create a record in the review_capacity DB table for this member with some defaults + let _ = add_prefs(&db_client, team_member.id.unwrap() as i64).await?; + info!("Added team member {}", &team_member.login); + } + } + + // 4. delete prefs for members not present in the team roster anymore + let removed_members = team_review_prefs + .iter() + .filter(|tm| { + !team_members.contains(&User { + id: Some(tm.user_id), + login: tm.username.clone(), + }) + }) + .map(|tm| tm.user_id) + .collect::>(); + if !removed_members.is_empty() { + let _ = delete_prefs(&db_client, &removed_members).await?; + info!("Delete preferences for team members {:?}", &removed_members); + } + info!("Finished updating review prefs for team {}", team); + } + + info!("Import/Sync job finished"); + Ok(()) +} From e8cc894f740a708870fa4472d66774c6b5485858 Mon Sep 17 00:00:00 2001 From: apiraino Date: Thu, 28 Sep 2023 22:05:44 +0200 Subject: [PATCH 09/10] Update documentation --- pr_prefs_backoffice.md | 14 ++++++-------- src/bin/import-users-from-github.rs | 21 +++++++-------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/pr_prefs_backoffice.md b/pr_prefs_backoffice.md index 46ace568..b6b2d869 100644 --- a/pr_prefs_backoffice.md +++ b/pr_prefs_backoffice.md @@ -15,12 +15,11 @@ This backoffice will set one cookie (`triagebot.session`) to understand if a use Access authorization is handled by GitHub, so users will need to be logged in GitHub and authorize this Github Application. -Access to this backoffice is granted only to GitHub users that are members of a Rust project team (teams are defined [here](https://github.com/rust-lang/team/tree/HEAD/teams)). Only specific Rust teams are allowed to use -this backoffice (mostly for testing purposes, to switch users to the new workflow slowly). Teams allowed are defined in the env var `NEW_PR_ASSIGNMENT_TEAMS` (see `.env.sample`). +Access to this backoffice is granted only to GitHub users that are members of a Rust project team (teams are defined [here](https://github.com/rust-lang/team/tree/HEAD/teams)). Only specific Rust teams are allowed to use this backoffice (mostly for testing purposes, to gradually onboard users to the new workflow). Teams allowed are defined in the env var `NEW_PR_ASSIGNMENT_TEAMS` (see `.env.sample`). Teams members are expected to set their own review preferences using this backoffice. In case a team member didn't yet set their own preferences, these defaults will be applied: - Max 5 pull requests assigned (see constant `PREF_MAX_ASSIGNED_PRS`) -- 20 days before a notification is sent for a pull request assigned that is waiting for review (see constant `PREF_ALLOW_PING_AFTER_DAYS`) +- 15 days before a notification is sent for a pull request assigned that is waiting for review (see constant `PREF_ALLOW_PING_AFTER_DAYS`) ## How to locally run this backoffice @@ -31,11 +30,10 @@ Teams members are expected to set their own review preferences using this backof ## TODO -- [ ] Build some tooling to import members from a team `.toml` file (SQL fixtures, CLI, or else) -- [ ] Set some defaults in the DB table structure for contributors that did not set any for themselves. -- [ ] Handle cleanup of the preferences DB table for team members not existing anymore in the teams .toml: delete their assignments, PRs go back to the pool of those needing an assignee -- [ ] Cache somehow teams .toml download from github to avoid retrieving those `.toml` files too often +- [x] Build some tooling to import members from teams +- [x] Set some defaults in the DB table structure for contributors that did not set any for themselves. +- [ ] Handle cleanup of the preferences DB table for team members not existing anymore (or were moved to "alumni") in the teams: delete their assignments, PRs go back to the pool of those needing an assignee - [ ] maybe more input validation, see `validate_data()` in `./src/main.rs` - [ ] Now we are handling contributors workload for a single team. Some contributors work across teams. Make this backoffice aware of other teams and show the actual workload of contributors - +- [ ] Finally, disable the "on vacation" flag in .toml files (revert [triagebot#1712](https://github.com/rust-lang/triagebot/pull/1712)). Give notice on Zulip (f.e. [here](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/.22on_vacation.22.20triagebot.20config)) diff --git a/src/bin/import-users-from-github.rs b/src/bin/import-users-from-github.rs index d5437137..d40befcf 100644 --- a/src/bin/import-users-from-github.rs +++ b/src/bin/import-users-from-github.rs @@ -5,17 +5,10 @@ use triagebot::github::User; use triagebot::handlers::review_prefs::{add_prefs, delete_prefs, get_prefs}; use triagebot::{db::make_client, github}; -// - 1. Download the teams and retrieve those listed in $NEW_PR_ASSIGNMENT_TEAMS -// - 2. match the team `people.members` with the table `users` and `review_capacity` -// - 3. Follow this scheme to perform a sync - -// | - | triagebot.users | triagebot.review_capacity | action | -// |-------|-----------------|---------------------------|---------------------------------------| -// | V | V | X | add | -// | V | X | X | add | -// | V | X | V | add | -// | X | V | X | do nothing | -// | X | X | V | remove from triagebot.review_capacity | +// Import and synchronization: +// 1. Download teams and retrieve those listed in $NEW_PR_ASSIGNMENT_TEAMS +// 2. Add missing team members to the review preferences table +// 3. Delete preferences for members not present in the team roster anymore #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { @@ -43,7 +36,7 @@ async fn main() -> anyhow::Result<()> { .collect::>(); debug!("Team {} members loaded: {:?}", team, team_members); - // 2. get team members review capacity + // get team members review capacity let team_review_prefs = get_prefs( &db_client, &team_members @@ -55,7 +48,7 @@ async fn main() -> anyhow::Result<()> { ) .await; - // 3. Add missing team members to the review preferences table + // 2. Add missing team members to the review preferences table for member in &team_members { if !team_review_prefs .iter() @@ -84,7 +77,7 @@ async fn main() -> anyhow::Result<()> { } } - // 4. delete prefs for members not present in the team roster anymore + // 3. delete prefs for members not present in the team roster anymore let removed_members = team_review_prefs .iter() .filter(|tm| { From d0faf47dd3c131919fecb238de4ca18fa448c80a Mon Sep 17 00:00:00 2001 From: apiraino Date: Wed, 8 Nov 2023 20:29:02 +0100 Subject: [PATCH 10/10] Add new PR review assignment This is the implementation of a new workflow for assigning pull requests to the Rust project contributors. This new workflow will assign a pull request to be reviewed based on the number of assigned pull requests to team members. Everytime a pull request assignment is invoked with `r? ` or `r? ` the Triagebot will check the current workload of the candidates and assign the pull request to the team member less busy. The new workflow is DISABLED by default. It can be enabled by setting the env variable `USE_NEW_PR_ASSIGNMENT` (any value) and restarting the Triagebot. Teams that are subject to the new PR review assignment are listed as a comma separated list in the env variable `NEW_PR_ASSIGNMENT_TEAMS`. See `.env.sample` for a usage example. Team members workload is tracked in a new DB table `review_capacity`. Both the initial population and the synchronization of this table are handled by a command line tool that will speak to the Triagebot through a number of HTTP endpoints. These HTTP endpoints must be PRIVATE and accessible ONLY to the sync tool. This should be handled at the infrastructure level, the endpoints are not authenticated. --- .env.sample | 14 +- Cargo.lock | 1200 +++++++++++++-------------- Cargo.toml | 2 - README.md | 6 - pr_prefs_backoffice.md | 39 - src/bin/import-users-from-github.rs | 10 +- src/db.rs | 2 +- src/github.rs | 67 -- src/handlers/assign.rs | 2 - src/handlers/review_prefs.rs | 14 - src/lib.rs | 107 +-- src/main.rs | 296 +------ 12 files changed, 600 insertions(+), 1159 deletions(-) delete mode 100644 pr_prefs_backoffice.md diff --git a/.env.sample b/.env.sample index dc1d3834..aa45ea2c 100644 --- a/.env.sample +++ b/.env.sample @@ -10,17 +10,10 @@ GITHUB_WEBHOOK_SECRET=MUST_BE_CONFIGURED # This is useful if you configure a webhook from GitHub and want to use your local triagebot as receiving end of the GitHub webhooks. TRIAGEBOT_HOST=http://7e9ea9dc.ngrok-free.app -# These are identifiers about your Github App for the pull request prefs backoffice (see pr_prefs_backoffice.md). Needed only if -# want to hack locally on the backoffice. -# CLIENT_ID is public -# CLIENT_SECRET must be kept, well, secret. -CLIENT_ID="xxx" -CLIENT_SECRET="yyy" - # Flag to enable the new pull request assignment workflow (see pr_prefs_backoffice.md). # If this env var is UNSET, the old pull request assignment is used (basically random assignment). # If this env var is SET, the new pull request assignment reads the Rust contributors preferences and assigns PRs accordingly. -USE_NEW_PR_ASSIGNMENT=yay +USE_NEW_PR_ASSIGNMENT=yes # A comma separated list of teams that are allowed to use the new PR assignment workflow. # Used to limit the number of users during the test phase. @@ -28,11 +21,6 @@ USE_NEW_PR_ASSIGNMENT=yay # https://github.com/rust-lang/team/tree/master/teams NEW_PR_ASSIGNMENT_TEAMS=compiler,compiler-contributors -# This is a secret used to create a checksum of the login cookie when accessing -# the pull request preferences backoffice. Only the server knows this secret. -# Could be generated with something like: openssl rand -hex 20 -BACKOFFICE_SECRET=xxx - # If you are running a bot on non-rustbot account, # this allows to configure that username which the bot will respond to. # For example write blahblahblah here, if you want for this bot to diff --git a/Cargo.lock b/Cargo.lock index 329498a6..d82a6940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ "gimli", ] @@ -19,20 +19,26 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "0.7.18" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] -name = "ansi_term" -version = "0.12.1" +name = "android-tzdata" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "winapi", + "libc", ] [[package]] @@ -43,9 +49,9 @@ checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "arc-swap" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5d78ce20460b82d3fa150275ed9d55e21064fc7951177baacf86a145c4a4b1f" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" [[package]] name = "ascii" @@ -55,13 +61,13 @@ checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] name = "async-trait" -version = "0.1.53" +version = "0.1.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] @@ -72,30 +78,30 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "backtrace" -version = "0.3.64" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" dependencies = [ "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide 0.4.4", + "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" +checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" [[package]] name = "bitflags" @@ -105,78 +111,55 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "block-buffer" -version = "0.7.3" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" -dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array 0.12.4", -] +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "block-buffer" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "block-padding" -version = "0.1.5" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "byte-tools", + "generic-array", ] [[package]] name = "bstr" -version = "0.2.17" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "c79ad7fb2dd38f3dabd76b09c6a5a20c038fc0213ef1e9afd30eb777f120f019" dependencies = [ "memchr", + "serde", ] [[package]] name = "bumpalo" -version = "3.9.1" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" -version = "1.0.73" +version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] [[package]] name = "cfg-if" @@ -186,25 +169,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.19" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" dependencies = [ - "libc", - "num-integer", + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", "serde", - "time 0.1.44", - "winapi", -] - -[[package]] -name = "cmake" -version = "0.1.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" -dependencies = [ - "cc", + "wasm-bindgen", + "windows-targets", ] [[package]] @@ -220,24 +195,6 @@ dependencies = [ "unreachable", ] -[[package]] -name = "commoncrypto" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d056a8586ba25a1e4d61cb090900e495952c7886786fc55f909ab2f819b69007" -dependencies = [ - "commoncrypto-sys", -] - -[[package]] -name = "commoncrypto-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fed34f46747aa73dfaa578069fd8279d2818ade2b55f38f22a9401c7f4083e2" -dependencies = [ - "libc", -] - [[package]] name = "comrak" version = "0.8.2" @@ -268,9 +225,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "counter" @@ -283,22 +240,13 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.2" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "cron" version = "0.12.0" @@ -310,43 +258,21 @@ dependencies = [ "once_cell", ] -[[package]] -name = "crossbeam-utils" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" -dependencies = [ - "cfg-if", - "lazy_static", -] - [[package]] name = "crypto-common" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.5", + "generic-array", "typenum", ] -[[package]] -name = "crypto-hash" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a77162240fd97248d19a564a565eb563a3f592b386e4136fb300909e67dddca" -dependencies = [ - "commoncrypto", - "hex 0.3.2", - "openssl", - "winapi", -] - [[package]] name = "cynic" -version = "2.2.2" +version = "2.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a3b1c069f38d62c0795fd74df7a55bde42d3ff44e58a00576d1e5c65222fa8" +checksum = "b1afa0591b1021e427e548a1f0f147fe6168f6c7c7f7006bace77f28856051b8" dependencies = [ "cynic-proc-macros", "serde", @@ -356,9 +282,9 @@ dependencies = [ [[package]] name = "cynic-codegen" -version = "2.2.2" +version = "2.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aefc758a26044bd02a2a0ecb871b158abcb98c92d93d7bc40750285064de602c" +checksum = "70a1bb05cc554f46079d0fa72abe995a2d32d0737d410a41da75b31e3f7ef768" dependencies = [ "counter", "darling", @@ -367,17 +293,17 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 1.0.91", + "syn 1.0.109", ] [[package]] name = "cynic-proc-macros" -version = "2.2.2" +version = "2.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c948605f5b7ae37c2e70b42c71679172c4a147351ee1b475578ea8f7d70680db" +checksum = "aa595c4ed7a5374e0e58c5c34f9d93bd6b7d45062790963bd4b4c3c0bf520c4d" dependencies = [ "cynic-codegen", - "syn 1.0.91", + "syn 1.0.109", ] [[package]] @@ -401,7 +327,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 1.0.91", + "syn 1.0.109", ] [[package]] @@ -412,55 +338,29 @@ checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ "darling_core", "quote", - "syn 1.0.91", + "syn 1.0.109", ] [[package]] name = "deranged" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" - -[[package]] -name = "digest" -version = "0.8.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" dependencies = [ - "generic-array 0.12.4", + "powerfmt", ] [[package]] name = "digest" -version = "0.10.3" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.2", + "block-buffer", "crypto-common", "subtle", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "doc-comment" version = "0.3.3" @@ -481,9 +381,9 @@ checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "encoding_rs" -version = "0.8.31" +version = "0.8.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" dependencies = [ "cfg-if", ] @@ -495,10 +395,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] -name = "fake-simd" -version = "0.1.2" +name = "errno" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" +checksum = "7c18ee0ed65a5f1f81cac6b1d213b69c35fa47d4252ad41f1486dbd8226fe36e" +dependencies = [ + "libc", + "windows-sys", +] [[package]] name = "fallible-iterator" @@ -508,23 +412,15 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.7.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" -dependencies = [ - "instant", -] +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] -name = "flate2" -version = "1.0.27" +name = "finl_unicode" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010" -dependencies = [ - "crc32fast", - "libz-ng-sys", - "miniz_oxide 0.7.1", -] +checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" [[package]] name = "fnv" @@ -558,9 +454,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" dependencies = [ "futures-channel", "futures-core", @@ -573,9 +469,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" dependencies = [ "futures-core", "futures-sink", @@ -583,15 +479,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" [[package]] name = "futures-executor" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" dependencies = [ "futures-core", "futures-task", @@ -600,38 +496,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" [[package]] name = "futures-macro" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" dependencies = [ "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] name = "futures-sink" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" [[package]] name = "futures-task" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" [[package]] name = "futures-util" -version = "0.3.21" +version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" dependencies = [ "futures-channel", "futures-core", @@ -647,18 +543,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.12.4" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -675,20 +562,20 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.6" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" dependencies = [ "cfg-if", "libc", - "wasi 0.10.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "gimli" -version = "0.26.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" [[package]] name = "github-graphql" @@ -700,15 +587,15 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.8" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" dependencies = [ "aho-corasick", "bstr", @@ -753,15 +640,15 @@ dependencies = [ "indexmap", "slab", "tokio", - "tokio-util 0.7.1", + "tokio-util", "tracing", ] [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -771,18 +658,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" +checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" @@ -796,7 +674,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest", ] [[package]] @@ -835,9 +713,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" @@ -856,7 +734,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -865,9 +743,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http", @@ -904,6 +782,29 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -922,11 +823,10 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.18" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" dependencies = [ - "crossbeam-utils", "globset", "lazy_static", "log", @@ -940,50 +840,41 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.8.1" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", "serde", ] -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - [[package]] name = "ipnet" -version = "2.4.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.1" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" -version = "0.3.57" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" dependencies = [ "wasm-bindgen", ] @@ -994,9 +885,9 @@ version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "pem", - "ring", + "ring 0.16.20", "serde", "serde_json", "simple_asn1", @@ -1010,25 +901,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" [[package]] -name = "libz-ng-sys" -version = "1.1.12" +name = "linux-raw-sys" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dd9f43e75536a46ee0f92b758f6b63846e594e86638c61a9251338a65baea63" -dependencies = [ - "cmake", - "libc", -] +checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" [[package]] name = "lock_api" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" dependencies = [ "autocfg", "scopeguard", @@ -1040,41 +927,36 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "md-5" -version = "0.10.1" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "digest 0.10.3", + "cfg-if", + "digest", ] [[package]] name = "memchr" -version = "2.4.1" +version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" @@ -1082,16 +964,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" -[[package]] -name = "miniz_oxide" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1103,32 +975,20 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52da4364ffb0e4fe33a9841a98a3f3014fb964045ce4f7a45a398243c8d6b0c9" +checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", - "log", - "miow", - "ntapi", - "wasi 0.11.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "miow" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" -dependencies = [ - "winapi", + "wasi", + "windows-sys", ] [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -1144,20 +1004,21 @@ dependencies = [ [[package]] name = "nom" -version = "7.1.1" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] -name = "ntapi" -version = "0.3.7" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" dependencies = [ + "overload", "winapi", ] @@ -1174,9 +1035,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", @@ -1184,18 +1045,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", @@ -1203,9 +1064,9 @@ dependencies = [ [[package]] name = "object" -version = "0.27.1" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" +checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "memchr", ] @@ -1218,7 +1079,7 @@ checksum = "bbed1b1298bc70dd4ae89fd44dd7c13f0a1c80a506d731eb1b939f14f5e367de" dependencies = [ "arc-swap", "async-trait", - "base64 0.21.4", + "base64 0.21.5", "bytes", "cfg-if", "chrono", @@ -1253,26 +1114,32 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - [[package]] name = "openssl" -version = "0.10.38" +version = "0.10.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", "once_cell", + "openssl-macros", "openssl-sys", ] +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -1281,40 +1148,43 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.72" +version = "0.9.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", "vcpkg", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" -version = "0.11.2" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ - "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" dependencies = [ "cfg-if", - "instant", "libc", "redox_syscall", "smallvec", - "winapi", + "windows-targets", ] [[package]] @@ -1332,7 +1202,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64 0.13.0", + "base64 0.13.1", ] [[package]] @@ -1343,18 +1213,20 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.1.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" dependencies = [ + "memchr", + "thiserror", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.1.0" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" dependencies = [ "pest", "pest_generator", @@ -1362,42 +1234,42 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.1.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] name = "pest_meta" -version = "2.1.3" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" dependencies = [ - "maplit", + "once_cell", "pest", - "sha-1", + "sha2", ] [[package]] name = "phf" -version = "0.10.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_shared", ] [[package]] name = "phf_shared" -version = "0.10.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] @@ -1419,7 +1291,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] @@ -1436,19 +1308,20 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "postgres-derive" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c2c18e40b92144b05e6f3ae9d1ee931f0d1afa9410ac8b97486c6eaaf91201" +checksum = "83145eba741b050ef981a9a1838c843fa7665e154383325aa8b440ae703180a2" dependencies = [ + "heck", "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] @@ -1466,11 +1339,11 @@ dependencies = [ [[package]] name = "postgres-protocol" -version = "0.6.4" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "878c6cbf956e03af9aa8204b407b9cbf47c072164800aa918c516cd4b056c50c" +checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" dependencies = [ - "base64 0.13.0", + "base64 0.21.5", "byteorder", "bytes", "fallible-iterator", @@ -1484,9 +1357,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73d946ec7d256b04dfadc4e6a3292324e6f417124750fc5c0950f981b703a0f1" +checksum = "8d2234cdee9408b523530a9b6d2d6b373d1db34f6a8e51dc03ded1828d7fb67c" dependencies = [ "bytes", "chrono", @@ -1498,17 +1371,23 @@ dependencies = [ "uuid", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.67" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1557,42 +1436,32 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" -version = "0.2.13" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom", - "redox_syscall", - "thiserror", -] - [[package]] name = "regex" -version = "1.6.0" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-automata 0.4.3", + "regex-syntax 0.8.2", ] [[package]] @@ -1601,31 +1470,39 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", ] [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.20" +version = "0.11.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", "bytes", "encoding_rs", "futures-core", @@ -1646,6 +1523,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -1665,12 +1543,26 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb0205304757e5d899b9c2e448b867ffd03ae7f988002e47cd24954391394d0b" +dependencies = [ + "cc", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys", +] + [[package]] name = "route-recognizer" version = "0.3.1" @@ -1680,7 +1572,7 @@ checksum = "afab94fb28594581f62d981211a9a4d53cc8130bbcbbb89a0440d9b8e81a7746" [[package]] name = "rust_team_data" version = "1.0.0" -source = "git+https://github.com/rust-lang/team#49683ec8af9ca6b546b3af516ea4654424e302eb" +source = "git+https://github.com/rust-lang/team#b347bce06a78761441147811426d7043ab76b077" dependencies = [ "indexmap", "serde", @@ -1688,18 +1580,31 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" +dependencies = [ + "bitflags 2.4.1", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] [[package]] name = "rustls" -version = "0.21.7" +version = "0.21.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "446e14c5cda4f3f30fe71863c34ec70f5ac79d6087097ad0bb433e1be5edf04c" dependencies = [ "log", - "ring", + "ring 0.17.5", "rustls-webpki", "sct", ] @@ -1722,24 +1627,24 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" dependencies = [ - "base64 0.21.4", + "base64 0.21.5", ] [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] name = "ryu" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" @@ -1752,28 +1657,27 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.19" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "lazy_static", - "winapi", + "windows-sys", ] [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sct" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring", - "untrusted", + "ring 0.17.5", + "untrusted 0.9.0", ] [[package]] @@ -1787,9 +1691,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.6.1" +version = "2.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -1800,9 +1704,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" dependencies = [ "core-foundation-sys", "libc", @@ -1810,29 +1714,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.192" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" dependencies = [ "itoa", "ryu", @@ -1841,10 +1745,11 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.7" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7868ad3b8196a8a0aea99a8220b124278ee5320a55e4fde97794b6f85b1a377" +checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335" dependencies = [ + "itoa", "serde", ] @@ -1860,34 +1765,22 @@ dependencies = [ "serde", ] -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer 0.7.3", - "digest 0.8.1", - "fake-simd", - "opaque-debug", -] - [[package]] name = "sha2" -version = "0.10.2" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest", ] [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -1907,26 +1800,29 @@ dependencies = [ "num-bigint", "num-traits", "thiserror", - "time 0.3.28", + "time", ] [[package]] name = "siphasher" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "slab" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] [[package]] name = "smallvec" -version = "1.8.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "snafu" @@ -1948,25 +1844,41 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 1.0.91", + "syn 1.0.109", ] [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", ] +[[package]] +name = "socket2" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" +dependencies = [ + "libc", + "windows-sys", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "static_assertions" version = "1.1.0" @@ -1975,10 +1887,11 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "bb41d74e231a107a1b4ee36bd1214b11285b77768d2e3824aedafa988fd36ee6" dependencies = [ + "finl_unicode", "unicode-bidi", "unicode-normalization", ] @@ -1991,51 +1904,71 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "subtle" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "1.0.91" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] name = "syn" -version = "2.0.37" +version = "2.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" -version = "3.3.0" +version = "3.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" dependencies = [ "cfg-if", "fastrand", - "libc", "redox_syscall", - "remove_dir_all", - "winapi", + "rustix", + "windows-sys", ] [[package]] name = "tera" -version = "1.15.0" +version = "1.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3cac831b615c25bcef632d1cabf864fa05813baad3d526829db18eb70e8b58d" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" dependencies = [ "globwalk", "lazy_static", @@ -2049,52 +1982,43 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.30" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.30" +version = "1.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] [[package]] name = "time" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" -dependencies = [ - "libc", - "wasi 0.10.0+wasi-snapshot-preview1", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" dependencies = [ "deranged", "itoa", + "powerfmt", "serde", "time-core", "time-macros", @@ -2102,49 +2026,49 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "tinyvec" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.17.0" +version = "1.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af73ac49756f3f7c01172e34a23e5d0216f6c32333757c2c61feb2bbff5a5ee" +checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653" dependencies = [ + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", "pin-project-lite", - "socket2", + "socket2 0.5.5", "tokio-macros", - "winapi", + "windows-sys", ] [[package]] @@ -2159,20 +2083,20 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", ] [[package]] name = "tokio-native-tls" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", @@ -2180,15 +2104,16 @@ dependencies = [ [[package]] name = "tokio-postgres" -version = "0.7.5" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6c8b33df661b548dcd8f9bf87debb8c56c05657ed291122e1188698c2ece95" +checksum = "d340244b32d920260ae7448cb72b6e238bddc3d4f7603394e7dd46ed8e48f5b8" dependencies = [ "async-trait", "byteorder", "bytes", "fallible-iterator", - "futures", + "futures-channel", + "futures-util", "log", "parking_lot", "percent-encoding", @@ -2196,9 +2121,11 @@ dependencies = [ "pin-project-lite", "postgres-protocol", "postgres-types", - "socket2", + "rand", + "socket2 0.5.5", "tokio", - "tokio-util 0.6.9", + "tokio-util", + "whoami", ] [[package]] @@ -2213,23 +2140,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.6.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e99e1983e5d376cd8eb4b66604d2e99e79f5bd988c3055891dcd8c9e2604cc0" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "log", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764" +checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" dependencies = [ "bytes", "futures-core", @@ -2241,9 +2154,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] @@ -2259,7 +2172,7 @@ dependencies = [ "pin-project", "pin-project-lite", "tokio", - "tokio-util 0.7.1", + "tokio-util", "tower-layer", "tower-service", "tracing", @@ -2271,7 +2184,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" dependencies = [ - "bitflags 2.4.0", + "bitflags 2.4.1", "bytes", "futures-core", "futures-util", @@ -2286,23 +2199,22 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2311,20 +2223,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.37", + "syn 2.0.39", ] [[package]] name = "tracing-core" -version = "0.1.31" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -2332,24 +2244,24 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6923477a48e41c1951f1999ef8bb5a3023eb723ceadafe78ffb65dc366761e3" +checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.11" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bc28f93baff38037f64e6f43d34cfa1605f27a49c34e8a04c5e78b0babf2596" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ - "ansi_term", - "lazy_static", "matchers", + "nu-ansi-term", + "once_cell", "regex", "sharded-slab", "smallvec", @@ -2369,14 +2281,12 @@ dependencies = [ "chrono", "comrak", "cron", - "crypto-hash", "cynic", "dotenv", - "flate2", "futures", "github-graphql", "glob", - "hex 0.4.3", + "hex", "hyper", "ignore", "itertools", @@ -2409,9 +2319,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "twoway" @@ -2431,15 +2341,15 @@ checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d" [[package]] name = "typenum" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.3" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unchecked-index" @@ -2499,9 +2409,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" dependencies = [ "version_check", ] @@ -2529,15 +2439,9 @@ dependencies = [ [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode_categories" @@ -2560,6 +2464,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.4.1" @@ -2608,31 +2518,23 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", - "winapi", "winapi-util", ] [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] -[[package]] -name = "wasi" -version = "0.10.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2641,9 +2543,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.80" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2651,24 +2553,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.80" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" dependencies = [ "bumpalo", - "lazy_static", "log", + "once_cell", "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.30" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" dependencies = [ "cfg-if", "js-sys", @@ -2678,9 +2580,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.80" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2688,33 +2590,43 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.80" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn 1.0.91", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.80" +version = "0.2.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" +checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" [[package]] name = "web-sys" -version = "0.3.57" +version = "0.3.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "whoami" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2733,9 +2645,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] @@ -2746,6 +2658,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.51.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -2824,12 +2745,9 @@ dependencies = [ [[package]] name = "xdg" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4583db5cbd4c4c0303df2d15af80f0539db703fa1c68802d4cbbd2dd0f88f6" -dependencies = [ - "dirs", -] +checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" [[package]] name = "zeroize" diff --git a/Cargo.toml b/Cargo.toml index 01ac3ece..ef51aea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,8 +45,6 @@ ignore = "0.4.18" postgres-types = { version = "0.2.4", features = ["derive"] } cron = { version = "0.12.0" } bytes = "1.1.0" -crypto-hash = { version = "0.3.4", default-features = false } -flate2 = { version = "1.0.27", default-features = false, features = ["zlib-ng"] } [dependencies.serde] version = "1" diff --git a/README.md b/README.md index f8451723..85216d9a 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,6 @@ The general overview of what you will need to do: 6. Add a `triagebot.toml` file to the main branch of your GitHub repo with whichever services you want to try out. 7. Try interacting with your repo, such as issuing `@rustbot` commands or interacting with PRs and issues (depending on which services you enabled in `triagebot.toml`). Watch the logs from the server to see what's going on. -## The pull requests assignment preferences backoffice - -This is an administrative backoffice targeted at the Rust project contributors to set their preferences for pull request assignment. - -Read all the documentation [here](./pr_prefs_backoffice.md). - ### Configure a database To use Postgres, you will need to install it and configure it: diff --git a/pr_prefs_backoffice.md b/pr_prefs_backoffice.md deleted file mode 100644 index b6b2d869..00000000 --- a/pr_prefs_backoffice.md +++ /dev/null @@ -1,39 +0,0 @@ -# Pull request assignment preferences backoffice - -This is an administrative backoffice targeted at the Rust project contributors to set their preferences for pull request assignment. - -When assigning the review of pull requests, this backoffice allows contributors to: -- set themselves on leave for any amount of time. During this time off contributors won't be assigned any new pull request -- set the maximum number of pull requests assigned to them -- set the desired number of days before a pull request assigned for review to the contributor might be considered for a reminder -- allow a flag to make their own preferences visible to all team members or only to team leaders and system administrators - -This is a mostly static web page server-side generated, using the least amount possible of JavaScript. - -This backoffice will set one cookie (`triagebot.session`) to understand if a user is already logged in. The cookie expires after -1 hour and is renewed at every access. The cookie is set to `Secure=true`, `HttpOnly` and `SameSite=Strict`. - -Access authorization is handled by GitHub, so users will need to be logged in GitHub and authorize this Github Application. - -Access to this backoffice is granted only to GitHub users that are members of a Rust project team (teams are defined [here](https://github.com/rust-lang/team/tree/HEAD/teams)). Only specific Rust teams are allowed to use this backoffice (mostly for testing purposes, to gradually onboard users to the new workflow). Teams allowed are defined in the env var `NEW_PR_ASSIGNMENT_TEAMS` (see `.env.sample`). - -Teams members are expected to set their own review preferences using this backoffice. In case a team member didn't yet set their own preferences, these defaults will be applied: -- Max 5 pull requests assigned (see constant `PREF_MAX_ASSIGNED_PRS`) -- 15 days before a notification is sent for a pull request assigned that is waiting for review (see constant `PREF_ALLOW_PING_AFTER_DAYS`) - -## How to locally run this backoffice - -- Configure a webhook pointing to a local instance of the triagebot. Follow the instructions [in the README](https://github.com/rust-lang/triagebot#configure-webhook-forwarding). -- Configure a repository under your GitHub username and configure the same webhook URL in the "Webhooks" settings of the repository. -- Create a GiHub Application and configure the callback URL ([here](https://github.com/settings/apps)) pointing to your proxied triagebot backoffice using the path to the backoffice (ex. `http://7e9ea9dc.ngrok.io/github-hook/review-settings`) -- Start your local triagebot: load the environment variable from a file (make a copy of `.env.sample`) and run `RUST_LOG=DEBUG cargo run --bin triagebot` - -## TODO - -- [x] Build some tooling to import members from teams -- [x] Set some defaults in the DB table structure for contributors that did not set any for themselves. -- [ ] Handle cleanup of the preferences DB table for team members not existing anymore (or were moved to "alumni") in the teams: delete their assignments, PRs go back to the pool of those needing an assignee -- [ ] maybe more input validation, see `validate_data()` in `./src/main.rs` -- [ ] Now we are handling contributors workload for a single team. Some contributors work across teams. Make this backoffice aware of other teams and show the actual workload of contributors -- [ ] Finally, disable the "on vacation" flag in .toml files (revert [triagebot#1712](https://github.com/rust-lang/triagebot/pull/1712)). Give notice on Zulip (f.e. [here](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/.22on_vacation.22.20triagebot.20config)) - diff --git a/src/bin/import-users-from-github.rs b/src/bin/import-users-from-github.rs index d40befcf..e74119f7 100644 --- a/src/bin/import-users-from-github.rs +++ b/src/bin/import-users-from-github.rs @@ -1,9 +1,12 @@ use reqwest::Client; use tracing::{debug, info}; -use triagebot::db::notifications::record_username; +// TODO: this should become an HTTP API +// use triagebot::db::notifications::record_username; +use triagebot::github; use triagebot::github::User; -use triagebot::handlers::review_prefs::{add_prefs, delete_prefs, get_prefs}; -use triagebot::{db::make_client, github}; + +// TODO: these should become HTTP APIs +// use triagebot::handlers::review_prefs::{add_prefs, delete_prefs, get_prefs}; // Import and synchronization: // 1. Download teams and retrieve those listed in $NEW_PR_ASSIGNMENT_TEAMS @@ -16,7 +19,6 @@ async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt::init(); let gh = github::GithubClient::new_with_default_token(Client::new()); - let db_client = make_client().await.unwrap(); let teams_data = triagebot::team_data::teams(&gh).await?; // 1. get team members diff --git a/src/db.rs b/src/db.rs index 8045275f..e9ead05a 100644 --- a/src/db.rs +++ b/src/db.rs @@ -92,7 +92,7 @@ impl ClientPool { } } -pub async fn make_client() -> anyhow::Result { +async fn make_client() -> anyhow::Result { let db_url = std::env::var("DATABASE_URL").expect("needs DATABASE_URL"); if db_url.contains("rds.amazonaws.com") { let cert = &CERTIFICATE_PEM[..]; diff --git a/src/github.rs b/src/github.rs index bf13f43c..310d71c3 100644 --- a/src/github.rs +++ b/src/github.rs @@ -7,7 +7,6 @@ use hyper::header::HeaderValue; use once_cell::sync::OnceCell; use reqwest::header::{AUTHORIZATION, USER_AGENT}; use reqwest::{Client, Request, RequestBuilder, Response, StatusCode}; -use rust_team_data::v1::TeamMember; use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use std::{ @@ -168,25 +167,6 @@ impl GithubClient { let (body, _req_dbg) = self.send_req(req).await?; Ok(serde_json::from_slice(&body)?) } - - pub fn get_team_members<'a>( - &self, - admins: &mut Vec, - members: &mut Vec, - team_members: &Vec, - ) { - for tm in team_members { - let m = User { - id: Some(tm.github_id as i64), - login: tm.github.clone(), - }; - if tm.is_lead { - admins.push(m); - } else { - members.push(m); - } - } - } } impl User { @@ -2103,53 +2083,6 @@ impl GithubClient { .await .with_context(|| format!("{} failed to get repo", full_name)) } - - pub async fn get_profile(&self, access_token: &str) -> anyhow::Result { - let c = self - .client - .get("https://api.github.com/user") - .header("Authorization", format!("Bearer {}", access_token)) - .header("User-Agent", "rust-lang-triagebot"); - self.json::(c).await - } -} - -pub async fn exchange_code( - code: &str, - client_id: &str, - client_secret: &str, -) -> anyhow::Result { - let client = reqwest::Client::new(); - let payload = - serde_json::json!({"client_id":client_id, "client_secret":client_secret, "code":code}); - - let tk = client - .post("https://github.com/login/oauth/access_token") - .header("Accept", "application/json") - .json(&payload) - .send() - .await - .context("Failed to contact remote host") - .unwrap() - .json::() - .await - .context("Could not decode response") - .expect("Error while retrieving the GH token"); - - if let Some(err_msg) = tk.get("error_description").cloned() { - return Err(anyhow::Error::msg(err_msg)); - } - Ok(tk - .get("access_token") - .unwrap() - .to_string() - .replace("\"", "")) -} - -pub async fn get_gh_user(access_token: &str) -> anyhow::Result { - let client = reqwest::Client::new(); - let gh = GithubClient::new(client, access_token.to_string()); - gh.get_profile(access_token).await } #[derive(Debug, serde::Deserialize)] diff --git a/src/handlers/assign.rs b/src/handlers/assign.rs index f8431373..b99e5b87 100644 --- a/src/handlers/assign.rs +++ b/src/handlers/assign.rs @@ -528,8 +528,6 @@ pub(super) async fn handle_command( } } }; - // NOTE: this will not handle PR assignment requested from the web Github UI - // that case is handled in the review_prefs module set_assignee(issue, &ctx.github, &username).await; // This PR will be registered in the reviewer's work queue using a `IssuesAction::Assigned` // and its delegate `handlers::review_prefs::handle_input()` diff --git a/src/handlers/review_prefs.rs b/src/handlers/review_prefs.rs index 04a486d8..cdceb138 100644 --- a/src/handlers/review_prefs.rs +++ b/src/handlers/review_prefs.rs @@ -65,20 +65,6 @@ RETURNING u.username, r.*"; Ok(rec.into()) } -/// Return a team member identified by a checksum -pub async fn get_user(db_client: &DbClient, checksum: &str) -> anyhow::Result { - let q = " -SELECT username,r.* -FROM review_capacity r -JOIN users on r.user_id=users.user_id -WHERE r.checksum=$1"; - let rec = db_client - .query_one(q, &[&checksum]) - .await - .context("SQL error")?; - Ok(rec.into()) -} - /// Get review preferences for a number of team members /// - me: sort the current user at the top of the list /// - is_admin: if `true` return also preferences marked as not public diff --git a/src/lib.rs b/src/lib.rs index f1ade990..a42fbb5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -9,7 +9,7 @@ use anyhow::Context; use handlers::HandlerError; use interactions::ErrorComment; use serde::Serialize; -use std::{collections::HashMap, fmt}; +use std::fmt; use tracing as log; pub mod actions; @@ -309,111 +309,6 @@ impl ReviewCapacityUser { } } -impl From> for ReviewCapacityUser { - fn from(obj: HashMap) -> Self { - // Note: if user set themselves as inactive all other prefs will be ignored - let active = { - let _o = obj.get("active"); - if _o.is_none() || _o.unwrap() == "no" { - false - } else { - true - } - }; - - let assigned_prs = if active { - if let Some(x) = obj.get("assigned_prs") { - x.parse::() - .unwrap() - .split(",") - .map(|x| x.parse::().unwrap()) - .collect() - } else { - vec![] - } - } else { - vec![] - }; - - let num_assigned_prs = if active { - if let Some(x) = obj.get("num_assigned_prs") { - Some(x.parse::().unwrap()) - } else { - None - } - } else { - None - }; - - let max_assigned_prs = if active { - if let Some(x) = obj.get("max_assigned_prs") { - Some(x.parse::().unwrap_or(PREF_MAX_ASSIGNED_PRS)) - } else { - None - } - } else { - None - }; - - let pto_date_start = if active { - if let Some(x) = obj.get("pto_date_start") { - Some(x.parse::().unwrap()) - } else { - None - } - } else { - None - }; - - let pto_date_end = if active { - if let Some(x) = obj.get("pto_date_end") { - Some(x.parse::().unwrap()) - } else { - None - } - } else { - None - }; - - let allow_ping_after_days = if active { - if let Some(x) = obj.get("allow_ping_after_days") { - Some(x.parse::().unwrap_or(PREF_ALLOW_PING_AFTER_DAYS)) - } else { - None - } - } else { - None - }; - - let publish_prefs = { - let _obj = obj.get("publish_prefs"); - if _obj.is_none() || _obj.unwrap() == "no" { - false - } else { - true - } - }; - - let prefs = ReviewCapacityUser { - // fields we don't receive from the web form (they are here ignored) - username: String::new(), - id: uuid::Uuid::new_v4(), - checksum: None, - // fields received from the web form - user_id: obj.get("user_id").unwrap().parse::().unwrap(), - assigned_prs, - num_assigned_prs, - max_assigned_prs, - pto_date_start, - pto_date_end, - active, - allow_ping_after_days, - publish_prefs, - }; - prefs - } -} - impl From for ReviewCapacityUser { fn from(row: tokio_postgres::row::Row) -> Self { Self { diff --git a/src/main.rs b/src/main.rs index 6129a957..0b6aa0d2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,41 +1,25 @@ #![allow(clippy::new_without_default)] use anyhow::Context as _; -use chrono::{Duration, Utc}; -use crypto_hash::{hex_digest, Algorithm}; -use flate2::{write::GzEncoder, Compression}; use futures::future::FutureExt; use futures::StreamExt; use hyper::{header, Body, Request, Response, Server, StatusCode}; use reqwest::Client; use route_recognizer::Router; -use std::collections::HashMap; -use std::io::Write; use std::{env, net::SocketAddr, sync::Arc}; use tokio::{task, time}; use tower::{Service, ServiceExt}; use tracing as log; use tracing::Instrument; -use triagebot::actions::TEMPLATES; -use triagebot::github::User; -use triagebot::handlers::review_prefs::get_user; use triagebot::jobs::{jobs, JOB_PROCESSING_CADENCE_IN_SECS, JOB_SCHEDULING_CADENCE_IN_SECS}; use triagebot::ReviewCapacityUser; use triagebot::{ db, github, - handlers::review_prefs::{get_prefs, set_prefs}, + handlers::review_prefs::{add_prefs, delete_prefs, get_prefs, set_prefs}, handlers::Context, notification_listing, payload, EventName, }; -const TINFRA_ZULIP_LINK: &str = "https://rust-lang.zulipchat.com/#narrow/stream/242791-t-infra"; -const ERR_AUTH : &str = "Fatal error occurred during authentication. Please click here to retry. \ -

If the error persists, please contact an administrator on Zulip and provide your GitHub username."; -const ERR_NO_GH_USER: &str = "Fatal error: cannot load the backoffice. Please contact an administrator on Zulip and provide your GitHub username. \ - "; -const ERR_AUTH_UNAUTHORIZED : &str = "You are not allowed to enter this website. \ -

If you think this is a mistake, please contact an administrator on Zulip and provide your GitHub username."; - async fn handle_agenda_request(req: String) -> anyhow::Result { if req == "/agenda/lang/triage" { return triagebot::agenda::lang().call().await; @@ -50,15 +34,6 @@ async fn handle_agenda_request(req: String) -> anyhow::Result { anyhow::bail!("Unknown agenda; see /agenda for index.") } -fn validate_data(prefs: &ReviewCapacityUser) -> anyhow::Result<()> { - if prefs.pto_date_start > prefs.pto_date_end { - return Err(anyhow::anyhow!( - "pto_date_start cannot be bigger than pto_date_end" - )); - } - Ok(()) -} - async fn serve_req( req: Request, ctx: Arc, @@ -189,239 +164,41 @@ async fn serve_req( .unwrap()); } - if req.uri.path() == "/review-settings" { - let gh = github::GithubClient::new_with_default_token(Client::new()); - // check if we received a session cookie - let maybe_user_enc = match req.headers.get("Cookie") { - Some(cookie_string) => cookie_string - .to_str() - .unwrap() - .split(';') - .filter_map(|cookie| { - let c = cookie.split('=').map(|x| x.trim()).collect::>(); - if c[0] == "triagebot.session".to_string() { - Some(c[1]) - } else { - None - } - }) - .last(), - _ => None, - }; - - // Authentication: understand who this user is + // TODO: expose endpoint to GET team members review prefsa + if req.uri.path() != "/reviews-get-prefs" { let db_client = ctx.db.get().await; - let user = match maybe_user_enc { - Some(user_enc) => { - // We have a user in the cookie - // Verify who this user claims to be - // format is: {"checksum":"...", "exp":"...", "sub":"...", "uid":"..."} - let user_check: serde_json::Value = serde_json::from_str(user_enc).unwrap(); - let basic_check = create_cookie_content( - user_check["sub"].as_str().unwrap(), - user_check["uid"].as_i64().unwrap(), - ); - if basic_check["checksum"] != user_check["checksum"] { - return Ok(Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body(Body::from( - ERR_AUTH.replace("{tinfra_zulip_link}", TINFRA_ZULIP_LINK), - )) - .unwrap()); - } - - match get_user(&db_client, user_check["checksum"].as_str().unwrap()).await { - Ok(u) => User { - login: u.username, - id: Some(u.user_id), - }, - Err(err) => { - log::debug!("{:?}", err); - return Ok(Response::builder() - .status(StatusCode::FORBIDDEN) - .body(Body::empty()) - .unwrap()); - } - } - } - _ => { - // No username. Did we receive a `code` in the query URL (i.e. did the user went through the GH auth)? - let client_id = std::env::var("CLIENT_ID").expect("CLIENT_ID is not set"); - let client_secret = - std::env::var("CLIENT_SECRET").expect("CLIENT_SECRET is not set"); - if let Some(query) = req.uri.query() { - let code = - url::form_urlencoded::parse(query.as_bytes()).find(|(k, _)| k == "code"); - let login_link = format!( - "https://github.com/login/oauth/authorize?client_id={}", - client_id - ); - if let Some((_, code)) = code { - // generate a token to impersonate the user - let maybe_access_token = - github::exchange_code(&code, &client_id, &client_secret).await; - if let Err(err_msg) = maybe_access_token { - log::debug!("Github auth failed: {}", err_msg); - return Ok(Response::builder() - .status(StatusCode::OK) - .header(hyper::header::CONTENT_TYPE, "text/html") - .body(Body::from( - ERR_AUTH - .replace("{login_link}", &login_link) - .replace("{}", TINFRA_ZULIP_LINK), - )) - .unwrap()); - } - let access_token = maybe_access_token.unwrap(); - // Ok, we have an access token. Retrieve the GH username - match github::get_gh_user(&access_token).await { - Ok(user) => user, - Err(err) => { - log::debug!("Could not retrieve the user from GH: {:?}", err); - return Ok(Response::builder() - .status(StatusCode::OK) - .header(hyper::header::CONTENT_TYPE, "text/html") - .body(Body::from( - ERR_NO_GH_USER.replace("{}", TINFRA_ZULIP_LINK), - )) - .unwrap()); - } - } - } else { - // no code. Bail out - return Ok(Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body(Body::from( - ERR_AUTH - .replace("{login_link}", &login_link) - .replace("{tinfra_login_link}", TINFRA_ZULIP_LINK), - )) - .unwrap()); - } - } else { - // no code and no username received: we know nothing about this visitor. Redirect to GH login. - return Ok(Response::builder() - .status(StatusCode::MOVED_PERMANENTLY) - .header( - hyper::header::LOCATION, - format!( - "https://github.com/login/oauth/authorize?client_id={}", - client_id - ), - ) - .body(Body::empty()) - .unwrap()); - } - } - }; - - let mut members: Vec = vec![]; - // I am an hardcoded admin - let mut admins = vec![User { - id: Some(6098822), - login: "apiraino".to_string(), - }]; - - // get team members from github (HTTP raw file retrieval, no auth used) - let teams_data = triagebot::team_data::teams(&gh).await.unwrap(); - let x = std::env::var("NEW_PR_ASSIGNMENT_TEAMS") - .expect("NEW_PR_ASSIGNMENT_TEAMS env var must be set"); - let allowed_teams = x.split(",").collect::>(); - for allowed_team in &allowed_teams { - let team = teams_data.teams.get(*allowed_team).unwrap(); - gh.get_team_members(&mut admins, &mut members, &team.members); - } - - log::debug!( - "Allowed teams: {:?}, members loaded {:?}", - &allowed_teams, - members - ); - if !members.iter().any(|m| m.login == user.login) { - return Ok(Response::builder() - .status(StatusCode::UNAUTHORIZED) - .body(Body::from( - ERR_AUTH_UNAUTHORIZED.replace("{tinfra_zulip_link}", TINFRA_ZULIP_LINK), - )) - .unwrap()); - } - - // Here we have a validated username. From now on, we will trust this user - let is_admin = admins.contains(&user); - log::debug!("current user={}, is admin: {}", user.login, is_admin); - - let mut info_msg: &str = ""; - if req.method == hyper::Method::POST { - let mut c = body_stream; - let mut payload = Vec::new(); - while let Some(chunk) = c.next().await { - let chunk = chunk?; - payload.extend_from_slice(&chunk); - } - let prefs = url::form_urlencoded::parse(payload.as_ref()) - .into_owned() - .collect::>() - .into(); - - // TODO: maybe add more input validation - validate_data(&prefs).unwrap(); - - // save changes - set_prefs(&db_client, &prefs).await.unwrap(); - - info_msg = "Preferences saved"; - } + let users = &vec!["user1".to_string(), "user2".to_string()]; + let me = "current user"; + let is_admin = false; // unused for now (useful for the future backoffice) + let _todo = get_prefs(&db_client, users, me, is_admin).await; + return Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()); + } - // Query and return all team member prefs - let mut review_capacity = get_prefs( - &db_client, - &members.iter().map(|tm| tm.login.clone()).collect(), - &user.login, - is_admin, - ) - .await; - let curr_user_prefs = serde_json::json!(&review_capacity.swap_remove(0)); - let team_prefs = serde_json::json!(&review_capacity); - // log::debug!("My prefs: {:?}", curr_user_prefs); - // log::debug!("Other team prefs: {:?}", team_prefs); - - let mut context = tera::Context::new(); - context.insert("user_prefs", &curr_user_prefs); - context.insert("team_prefs", &team_prefs); - context.insert("message", &info_msg); - let body = TEMPLATES - .render("pr-prefs-backoffice.html", &context) - .unwrap(); - - let status_code = if req.method == hyper::Method::POST { - StatusCode::CREATED - } else { - StatusCode::OK - }; - let cookie_exp = Utc::now() + Duration::hours(1); - let cookie_content = format!( - "triagebot.session={}; Expires={}; Secure; HttpOnly; SameSite=Strict", - create_cookie_content(&user.login, user.id.unwrap()).to_string(), - // RFC 5322: Thu, 31 Dec 2023 23:00:00 GMT - cookie_exp.format("%a, %d %b %Y %H:%M:%S %Z") - ); - - // compress the response - let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); - encoder.write_all(body.as_bytes()).unwrap(); - let body_gzipped = encoder.finish().unwrap(); - let resp = Response::builder() - .header(hyper::header::CONTENT_TYPE, "text/html") - .header(hyper::header::CONTENT_ENCODING, "gzip") - .header( - hyper::header::SET_COOKIE, - header::HeaderValue::from_str(&cookie_content).unwrap(), - ) - .status(status_code) - .body(Body::from(body_gzipped)); + // TODO: expose endpoint to DELETE review prefs for a number of team members + if req.uri.path() != "/reviews-del-prefs" { + let db_client = ctx.db.get().await; + let user_ids = vec![123456]; + let _todo = delete_prefs(&db_client, &user_ids).await; + return Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()); + } - return Ok(resp.unwrap()); + // TODO: expose endpoint to SET review prefs for a team member + if req.uri.path() != "/reviews-set-prefs" { + let db_client = ctx.db.get().await; + let prefs = ReviewCapacityUser::default("it's a-me".to_string()); + let _todo = set_prefs(&db_client, &prefs).await; + return Ok(Response::builder() + .status(StatusCode::OK) + .body(Body::empty()) + .unwrap()); } + if req.uri.path() != "/github-hook" { return Ok(Response::builder() .status(StatusCode::NOT_FOUND) @@ -510,15 +287,6 @@ async fn serve_req( } } -/// iss=triagebot, sub=gh username, uid=gh user_id, exp=now+30', checksum=sha256(user data) -fn create_cookie_content(user_login: &str, user_id: i64) -> serde_json::Value { - let auth_secret = std::env::var("BACKOFFICE_SECRET").expect("BACKOFFICE_SECRET is not set"); - let exp = Utc::now() + Duration::minutes(30); - let digest = format!("{};{};{}", user_id, user_login, auth_secret); - let digest = hex_digest(Algorithm::SHA256, &digest.into_bytes()); - serde_json::json!({"iss":"triagebot", "sub":user_login, "uid": user_id, "exp":exp, "checksum":digest}) -} - async fn run_server(addr: SocketAddr) -> anyhow::Result<()> { let pool = db::ClientPool::new(); db::run_migrations(&*pool.get().await)