From f459fe47d12b0f925c4b369bd9614714e5a38bd3 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Feb 2022 10:30:27 +0200 Subject: [PATCH 01/33] add display for warning flash msgs --- peach-web/templates/snippets/flash_message.html.tera | 3 +++ 1 file changed, 3 insertions(+) diff --git a/peach-web/templates/snippets/flash_message.html.tera b/peach-web/templates/snippets/flash_message.html.tera index 0b7fc3b..ca56bfd 100644 --- a/peach-web/templates/snippets/flash_message.html.tera +++ b/peach-web/templates/snippets/flash_message.html.tera @@ -5,6 +5,9 @@ {%- elif flash_msg and flash_name == "info" %}
{{ flash_msg }}.
+{%- elif flash_msg and flash_name == "warning" %} + +
{{ flash_msg }}.
{%- elif flash_msg and flash_name == "error" %}
{{ flash_msg }}.
From a174027ff5eaf5299a0f2454367ced123962df6e Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Feb 2022 10:31:07 +0200 Subject: [PATCH 02/33] add context builders for sbot --- peach-web/src/context/mod.rs | 1 + peach-web/src/context/sbot.rs | 113 ++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 peach-web/src/context/sbot.rs diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs index d2d350c..691bc21 100644 --- a/peach-web/src/context/mod.rs +++ b/peach-web/src/context/mod.rs @@ -1,2 +1,3 @@ pub mod dns; pub mod network; +pub mod sbot; diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs new file mode 100644 index 0000000..ca9b3a3 --- /dev/null +++ b/peach-web/src/context/sbot.rs @@ -0,0 +1,113 @@ +use golgi::Sbot; +use peach_lib::sbot::{SbotConfig, SbotStatus}; +use rocket::serde::Serialize; + +use crate::{error::PeachWebError, utils}; + +// HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, + }; + + Ok(sbot_client) +} + +// CONTEXT STRUCTS AND BUILDERS + +#[derive(Debug, Serialize)] +pub struct ProfileContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // is this the local profile or the profile of a peer? + pub is_local_profile: bool, + // an ssb_id which may or may not be the local public key + pub id: Option, + pub name: Option, + pub description: Option, + pub image: Option, +} + +impl ProfileContext { + pub fn default() -> Self { + ProfileContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Profile".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + is_local_profile: true, + id: None, + name: None, + description: None, + image: None, + } + } + + pub async fn build(ssb_id: Option) -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // if an ssb_id has been provided to the context builder, we assume that + // the profile info being retrieved is for a peer (ie. not for our local + // profile) + let id = if ssb_id.is_some() { + context.is_local_profile = false; + ssb_id.unwrap() + } else { + // if an ssb_id has not been provided, retrieve the local id using whoami + context.is_local_profile = true; + sbot_client.whoami().await? + }; + + // retrieve the profile info for the given id + let info = sbot_client.get_profile_info(&id).await?; + // set each context field accordingly + for (key, val) in info { + match key.as_str() { + "name" => context.name = Some(val), + "description" => context.description = Some(val), + "image" => context.image = Some(val), + _ => (), + } + } + context.id = Some(id); + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} From 17ea3e7f44807af256553508eb7cae5a959cba22 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Feb 2022 10:31:31 +0200 Subject: [PATCH 03/33] add golgi dependency and error variant --- peach-web/Cargo.toml | 1 + peach-web/src/error.rs | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index ddd4b0d..4183c91 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -36,6 +36,7 @@ maintenance = { status = "actively-developed" } [dependencies] env_logger = "0.8" +golgi = { path = "../../../playground/rust/golgi" } lazy_static = "1.4.0" log = "0.4" nest = "1.0.0" diff --git a/peach-web/src/error.rs b/peach-web/src/error.rs index 6288b6b..10d532d 100644 --- a/peach-web/src/error.rs +++ b/peach-web/src/error.rs @@ -1,5 +1,6 @@ //! Custom error type representing all possible error variants for peach-web. +use golgi::GolgiError; use peach_lib::error::PeachError; use peach_lib::{serde_json, serde_yaml}; use serde_json::error::Error as JsonError; @@ -8,6 +9,7 @@ use serde_yaml::Error as YamlError; /// Custom error type encapsulating all possible errors for the web application. #[derive(Debug)] pub enum PeachWebError { + Golgi(GolgiError), Json(JsonError), Yaml(YamlError), FailedToRegisterDynDomain(String), @@ -17,6 +19,7 @@ pub enum PeachWebError { impl std::error::Error for PeachWebError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match *self { + PeachWebError::Golgi(ref source) => Some(source), PeachWebError::Json(ref source) => Some(source), PeachWebError::Yaml(ref source) => Some(source), PeachWebError::FailedToRegisterDynDomain(_) => None, @@ -28,6 +31,7 @@ impl std::error::Error for PeachWebError { impl std::fmt::Display for PeachWebError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { + PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source), PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), PeachWebError::FailedToRegisterDynDomain(ref msg) => { @@ -38,6 +42,12 @@ impl std::fmt::Display for PeachWebError { } } +impl From for PeachWebError { + fn from(err: GolgiError) -> PeachWebError { + PeachWebError::Golgi(err) + } +} + impl From for PeachWebError { fn from(err: JsonError) -> PeachWebError { PeachWebError::Json(err) From 68c926609e1d55715ded55962935fe48a504fb55 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Feb 2022 10:32:39 +0200 Subject: [PATCH 04/33] routes and logic for profiles and publishing name, description and post --- peach-web/src/main.rs | 2 - peach-web/src/router.rs | 15 +- peach-web/src/routes/scuttlebutt.rs | 253 ++++++++++++++---- .../templates/scuttlebutt/profile.html.tera | 22 +- 4 files changed, 229 insertions(+), 63 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 14a0a03..1e7f1b4 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -22,8 +22,6 @@ //! `Serialize`. The fields of the context object are available in the context //! of the template to be rendered. -#![feature(proc_macro_hygiene, decl_macro)] - mod context; pub mod error; mod router; diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index be05163..7d7df86 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -62,8 +62,19 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { .mount( "/scuttlebutt", routes![ - peers, friends, follows, followers, blocks, profile, private, follow, unfollow, - block, publish, + peers, + friends, + follows, + followers, + blocks, + profile, + update_profile, + update_profile_post, + private, + follow, + unfollow, + block, + publish, ], ) .mount("/status", routes![scuttlebutt_status]) diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 0d884a7..44dfada 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -1,8 +1,12 @@ //! Routes for Scuttlebutt related functionality. +use log::debug; +use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{ form::{Form, FromForm}, - get, post, + get, + http::RawStr, + post, request::FlashMessage, response::{Flash, Redirect}, serde::{Deserialize, Serialize}, @@ -10,8 +14,11 @@ use rocket::{ }; use rocket_dyn_templates::Template; -use crate::routes::authentication::Authenticated; -use crate::utils; +use crate::{ + context::{sbot, sbot::ProfileContext}, + routes::authentication::Authenticated, + utils, +}; // HELPERS AND ROUTES FOR /private @@ -113,18 +120,42 @@ pub struct Post { /// message describing the outcome of the action (may be successful or /// unsuccessful). #[post("/publish", data = "")] -pub fn publish(post: Form, _auth: Authenticated) -> Flash { +pub async fn publish(post: Form, _auth: Authenticated) -> Flash { let post_text = &post.text; - // perform the sbotcli publish action using post_text - // if successful, redirect to home profile page and flash "success" - // if error, redirect to home profile page and flash "error" - // redirect to the profile template without public key ("home" / local profile) - let pub_key: std::option::Option<&str> = None; - let profile_url = uri!(profile(pub_key)); - // consider adding the message reference to the flash message (or render it in the template for - // `profile` - Flash::success(Redirect::to(profile_url), "Published public post") + let url = uri!("/scuttlebutt/profile"); + + // retrieve go-sbot systemd process status + // TODO: handle unwrap properly + let sbot_status = SbotStatus::read().unwrap(); + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing new Scuttlebutt public post"); + match sbot_client.publish_post(post_text).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Published post")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to publish post: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // HELPERS AND ROUTES FOR /follow @@ -186,52 +217,170 @@ pub fn block(pub_key: Form, _auth: Authenticated) -> Flash Flash::success(Redirect::to(profile_url), success_msg) } -// HELPERS AND ROUTES FOR /profile +// ROUTES FOR /profile -#[derive(Debug, Serialize)] -pub struct ProfileContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A Scuttlebutt profile, specified by a public key. It may be our own profile +/// or the profile of a peer. If the public key query parameter is not provided, +/// the local profile is displayed (ie. the profile of the public key associated +/// with the local PeachCloud device). +// TODO: this query param approach is not going to work because some characters +// get escaped. maybe we can use a RawString type? +#[get("/profile?")] +pub async fn profile( + pub_key: Option, + flash: Option>, + _auth: Authenticated, +) -> Template { + // build the profile context object + let context = ProfileContext::build(pub_key).await; -impl ProfileContext { - pub fn build() -> ProfileContext { - ProfileContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/profile", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); + + // flash name and msg will be `Some` if the sbot is inactive (in + // that case, they are set by the context builder). + // otherwise, we need to assign the name and returned error msg + // to the flash. + if context.flash_name.is_none() || context.flash_msg.is_none() { + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + } + + Template::render("scuttlebutt/profile", &context) } } } -/// A Scuttlebutt profile, specified by a public key. It may be our own profile or the profile of a peer. If not public key query parameter is provided, the local profile is displayed (ie. the profile of the public key associated with the local PeachCloud device). -#[get("/profile?")] -pub fn profile( - pub_key: Option<&str>, - flash: Option, - _auth: Authenticated, -) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +// HELPERS AND ROUTES FOR /profile/update - let mut context = ProfileContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Profile".to_string()); - context.theme = Some(theme); +/// Serve a form for the purpose of updating the name and description for the +/// local Scuttlebutt profile. +#[get("/profile/update")] +pub async fn update_profile(flash: Option>, _auth: Authenticated) -> Template { + // build the profile context object + let context = ProfileContext::build(None).await; - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; - Template::render("scuttlebutt/profile", &context) + Template::render("scuttlebutt/update_profile", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = ProfileContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/update_profile", &context) + } + } +} + +#[derive(Debug, Deserialize, FromForm)] +pub struct Profile { + pub current_name: String, + pub current_description: String, + pub new_name: String, + pub new_description: String, +} + +/// Update the name and description for the local Scuttlebutt profile. +/// Redirects to profile page of the PeachCloud local identity with a flash +/// message describing the outcome of the action (may be successful or +/// unsuccessful). +#[post("/profile/update", data = "")] +pub async fn update_profile_post(profile: Form, _auth: Authenticated) -> Flash { + let url = uri!("/scuttlebutt/profile/update"); + + // retrieve go-sbot systemd process status + // TODO: handle unwrap properly + let sbot_status = SbotStatus::read().unwrap(); + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // allows us to track whether the name and / or description have been updated + let mut updated: bool = false; + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + // only update the name if it has changed + if profile.new_name != profile.current_name { + debug!("Publishing new Scuttlebutt profile name"); + let publish_name_res = sbot_client.publish_name(&profile.new_name).await; + if publish_name_res.is_err() { + return Flash::error( + Redirect::to(url), + format!("Failed to update name: {}", publish_name_res.unwrap_err()), + ); + } else { + updated = true + } + } + + // only update the description if it has changed + if profile.new_description != profile.current_description { + debug!("Publishing new Scuttlebutt profile description"); + let publish_description_res = sbot_client + .publish_description(&profile.new_description) + .await; + + if publish_description_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update description: {}", + publish_description_res.unwrap_err() + ), + ); + } else { + updated = true + } + } + + if updated { + return Flash::success(Redirect::to(url), "Profile updated"); + } else { + // no updates were made but no errors were encountered either + return Flash::success(Redirect::to(url), "Profile info unchanged"); + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // HELPERS AND ROUTES FOR /friends @@ -257,8 +406,8 @@ impl FriendsContext { } } -/// A list of friends (mutual follows), with each list item displaying the name, image and public -/// key of the peer. +/// A list of friends (mutual follows), with each list item displaying the +/// name, image and public key of the peer. #[get("/friends")] pub fn friends(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 74b1c31..e932acf 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -2,24 +2,30 @@ {%- block card %}
+ {# only render the profile info elements is the sbot is active #} + {%- if sbot_status and sbot_status.state == "active" %}
- Profile picture + + Edit + - Profile picture + Profile picture -

{ name }

- -

{ description }

+

{{ name }}

+ +

{{ description }}

+ {% if is_local_profile %} -
+ - +
+ {% else %}
@@ -27,6 +33,8 @@ Block Private Message
+ {%- endif %} + {%- endif %} {% include "snippets/flash_message" %}
From fe1da62058e24287c4ba596cec82f05b475b523d Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 11 Feb 2022 10:32:59 +0200 Subject: [PATCH 05/33] add template for profile update form --- .../scuttlebutt/update_profile.html.tera | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 peach-web/templates/scuttlebutt/update_profile.html.tera diff --git a/peach-web/templates/scuttlebutt/update_profile.html.tera b/peach-web/templates/scuttlebutt/update_profile.html.tera new file mode 100644 index 0000000..15114c3 --- /dev/null +++ b/peach-web/templates/scuttlebutt/update_profile.html.tera @@ -0,0 +1,24 @@ +{%- extends "nav" -%} +{%- block card %} + {# ASSIGN VARIABLES #} + {# ---------------- #} + +
+
+
+ + + + +
+ + +
+ + Cancel +
+
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} From 1a3ddccbd66db18192ac71258a79b182a74f35e3 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Feb 2022 10:21:58 +0200 Subject: [PATCH 06/33] add invite creation and display --- peach-web/src/router.rs | 2 + peach-web/src/routes/scuttlebutt.rs | 85 ++++++++++++++++++- .../templates/scuttlebutt/invites.html.tera | 20 +++++ .../templates/scuttlebutt/peers.html.tera | 1 + 4 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 peach-web/templates/scuttlebutt/invites.html.tera diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 7d7df86..1c2c84d 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -62,6 +62,8 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { .mount( "/scuttlebutt", routes![ + invites, + create_invite, peers, friends, follows, diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 44dfada..5188bd3 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -12,7 +12,7 @@ use rocket::{ serde::{Deserialize, Serialize}, uri, }; -use rocket_dyn_templates::Template; +use rocket_dyn_templates::{tera::Context, Template}; use crate::{ context::{sbot, sbot::ProfileContext}, @@ -20,6 +20,84 @@ use crate::{ utils, }; +// HELPERS AND ROUTES FOR INVITES + +#[get("/invites")] +pub fn invites(flash: Option, _auth: Authenticated) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("back", &Some("/scuttlebutt/peers".to_string())); + context.insert("title", &Some("Invites".to_string())); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + match flash.kind() { + // we've been passed a freshly-generated invite code (redirect from post) + "code" => { + context.insert("invite_code", &Some(flash.message().to_string())); + context.insert("flash_name", &Some("success".to_string())); + context.insert("flash_msg", &Some("Generated invite code".to_string())); + } + _ => { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + } + } + }; + + Template::render("scuttlebutt/invites", &context.into_json()) +} + +#[derive(Debug, Deserialize, FromForm)] +pub struct Invite { + pub uses: u16, +} + +#[post("/invites", data = "")] +pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash { + let uses = invite.uses; + + let url = uri!("/scuttlebutt/invites"); + + // retrieve go-sbot systemd process status + // TODO: handle unwrap properly + let sbot_status = SbotStatus::read().unwrap(); + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Generating Scuttlebutt invite code"); + match sbot_client.invite_create(uses).await { + // construct a custom flash msg to pass along the invite code + Ok(code) => Flash::new(Redirect::to(url), "code", code), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to create invite code: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } +} + // HELPERS AND ROUTES FOR /private #[derive(Debug, Serialize)] @@ -87,7 +165,8 @@ impl PeerContext { } } -/// A peer menu which allows navigating to lists of friends, follows, followers and blocks. +/// A peer menu which allows navigating to lists of friends, follows, followers +/// and blocks, as well as accessing the invite creation form. #[get("/peers")] pub fn peers(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme @@ -277,6 +356,8 @@ pub async fn update_profile(flash: Option>, _auth: Authenticate match context { // we were able to build the context without errors Ok(mut context) => { + context.back = Some("/scuttlebutt/profile".to_string()); + // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object diff --git a/peach-web/templates/scuttlebutt/invites.html.tera b/peach-web/templates/scuttlebutt/invites.html.tera new file mode 100644 index 0000000..3a0e960 --- /dev/null +++ b/peach-web/templates/scuttlebutt/invites.html.tera @@ -0,0 +1,20 @@ +{%- extends "nav" -%} +{%- block card %} + +
+
+
+ + + {% if invite_code %} +

{{ invite_code }}

+ {% endif %} +
+ + + Cancel +
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index b78906f..74d7284 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -9,6 +9,7 @@ Follows Followers Blocks + Invites From 4665a9e6faf49ab9c474e1bd32c4797a61c67323 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Feb 2022 10:22:21 +0200 Subject: [PATCH 07/33] fix icon permissions; remove executable --- peach-web/static/icons/pencil.svg | 0 peach-web/static/icons/user.svg | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 peach-web/static/icons/pencil.svg mode change 100755 => 100644 peach-web/static/icons/user.svg diff --git a/peach-web/static/icons/pencil.svg b/peach-web/static/icons/pencil.svg old mode 100755 new mode 100644 diff --git a/peach-web/static/icons/user.svg b/peach-web/static/icons/user.svg old mode 100755 new mode 100644 From a37288225ad4fd181a04d2f52de07615c7f58091 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Feb 2022 10:22:52 +0200 Subject: [PATCH 08/33] remove decimal from memory and blobstore size --- peach-web/templates/status/scuttlebutt.html.tera | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peach-web/templates/status/scuttlebutt.html.tera b/peach-web/templates/status/scuttlebutt.html.tera index 32d04bc..787f8a5 100644 --- a/peach-web/templates/status/scuttlebutt.html.tera +++ b/peach-web/templates/status/scuttlebutt.html.tera @@ -3,12 +3,12 @@ {# ASSIGN VARIABLES #} {# ---------------- #} {%- if sbot_status.memory -%} - {% set mem = sbot_status.memory / 1024 / 1024 | round -%} + {% set mem = sbot_status.memory / 1024 / 1024 | round | int -%} {%- else -%} {% set mem = "X" -%} {%- endif -%} {%- if sbot_status.blobstore -%} - {% set blobs = sbot_status.blobstore / 1024 / 1024 | round -%} + {% set blobs = sbot_status.blobstore / 1024 / 1024 | round | int -%} {%- else -%} {% set blobs = "X" -%} {%- endif -%} From 9013ccb3d6175ce4b394be81d3ae86e46078d727 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Feb 2022 11:54:08 +0200 Subject: [PATCH 09/33] determine blobstore path and mount blob file server --- peach-web/Cargo.toml | 2 ++ peach-web/src/error.rs | 42 ++++++++++++++++++++++++++--------------- peach-web/src/router.rs | 22 ++++++++++++++------- peach-web/src/utils.rs | 27 +++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 4183c91..74ba710 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -35,7 +35,9 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" } maintenance = { status = "actively-developed" } [dependencies] +dirs = "4.0.0" env_logger = "0.8" +#golgi = "0.1.0" golgi = { path = "../../../playground/rust/golgi" } lazy_static = "1.4.0" log = "0.4" diff --git a/peach-web/src/error.rs b/peach-web/src/error.rs index 10d532d..e20608d 100644 --- a/peach-web/src/error.rs +++ b/peach-web/src/error.rs @@ -9,21 +9,25 @@ use serde_yaml::Error as YamlError; /// Custom error type encapsulating all possible errors for the web application. #[derive(Debug)] pub enum PeachWebError { - Golgi(GolgiError), - Json(JsonError), - Yaml(YamlError), FailedToRegisterDynDomain(String), + Golgi(GolgiError), + HomeDir, + Json(JsonError), + OsString, PeachLib { source: PeachError, msg: String }, + Yaml(YamlError), } impl std::error::Error for PeachWebError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match *self { - PeachWebError::Golgi(ref source) => Some(source), - PeachWebError::Json(ref source) => Some(source), - PeachWebError::Yaml(ref source) => Some(source), PeachWebError::FailedToRegisterDynDomain(_) => None, + PeachWebError::Golgi(ref source) => Some(source), + PeachWebError::HomeDir => None, + PeachWebError::Json(ref source) => Some(source), + PeachWebError::OsString => None, PeachWebError::PeachLib { ref source, .. } => Some(source), + PeachWebError::Yaml(ref source) => Some(source), } } } @@ -31,13 +35,21 @@ impl std::error::Error for PeachWebError { impl std::fmt::Display for PeachWebError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match *self { - PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source), - PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), - PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), PeachWebError::FailedToRegisterDynDomain(ref msg) => { write!(f, "DYN DNS error: {}", msg) } + PeachWebError::Golgi(ref source) => write!(f, "Golgi error: {}", source), + PeachWebError::HomeDir => write!( + f, + "Filesystem error: failed to determine home directory path" + ), + PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), + PeachWebError::OsString => write!( + f, + "Filesystem error: failed to convert OsString to String for go-ssb directory path" + ), PeachWebError::PeachLib { ref source, .. } => write!(f, "{}", source), + PeachWebError::Yaml(ref source) => write!(f, "Serde YAML error: {}", source), } } } @@ -54,12 +66,6 @@ impl From for PeachWebError { } } -impl From for PeachWebError { - fn from(err: YamlError) -> PeachWebError { - PeachWebError::Yaml(err) - } -} - impl From for PeachWebError { fn from(err: PeachError) -> PeachWebError { PeachWebError::PeachLib { @@ -68,3 +74,9 @@ impl From for PeachWebError { } } } + +impl From for PeachWebError { + fn from(err: YamlError) -> PeachWebError { + PeachWebError::Yaml(err) + } +} diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 1c2c84d..fd990d4 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -1,13 +1,16 @@ use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; use rocket_dyn_templates::Template; -use crate::routes::{ - authentication::*, - catchers::*, - index::*, - scuttlebutt::*, - settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*}, - status::{device::*, network::*, scuttlebutt::*}, +use crate::{ + routes::{ + authentication::*, + catchers::*, + index::*, + scuttlebutt::*, + settings::{admin::*, dns::*, menu::*, network::*, scuttlebutt::*, theme::*}, + status::{device::*, network::*, scuttlebutt::*}, + }, + utils, }; /// Create a Rocket instance and mount PeachPub routes, fileserver and @@ -15,6 +18,10 @@ use crate::routes::{ /// settings and status routes related to networking and the device (memory, /// hard disk, CPU etc.). pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { + // set the `.ssb-go` path in order to mount the blob fileserver + let ssb_path = utils::get_go_ssb_path().expect("define ssb-go dir path"); + let blobstore = format!("{}/blobs/sha256", ssb_path); + rocket .mount( "/", @@ -81,6 +88,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { ) .mount("/status", routes![scuttlebutt_status]) .mount("/", FileServer::from("static")) + .mount("/blob", FileServer::from(blobstore).rank(-1)) .register("/", catchers![not_found, internal_error, forbidden]) .attach(Template::fairing()) } diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index fee26fa..dea8150 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,11 +1,36 @@ pub mod monitor; +use dirs; use log::info; +use peach_lib::sbot::SbotConfig; use rocket::response::{Redirect, Responder}; use rocket::serde::Serialize; use rocket_dyn_templates::Template; -use crate::THEME; +use crate::{error::PeachWebError, THEME}; + +// FILEPATH FUNCTIONS + +// return the path of the ssb-go directory +pub fn get_go_ssb_path() -> Result { + let go_ssb_path = match SbotConfig::read() { + Ok(conf) => conf.repo, + // return the default path if unable to read `config.toml` + Err(_) => { + // determine the home directory + let mut home_path = dirs::home_dir().ok_or_else(|| PeachWebError::HomeDir)?; + // add the go-ssb subdirectory + home_path.push(".ssb-go"); + // convert the PathBuf to a String + home_path + .into_os_string() + .into_string() + .map_err(|_| PeachWebError::OsString)? + } + }; + + Ok(go_ssb_path) +} // THEME FUNCTIONS From e05de8284de699d9f95fa5b398114d535c72e012 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 23 Feb 2022 11:54:34 +0200 Subject: [PATCH 10/33] get profile image blob path and render in template --- peach-web/src/context/sbot.rs | 12 +++++++++++- peach-web/templates/scuttlebutt/profile.html.tera | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index ca9b3a3..f8f9b0d 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -40,6 +40,8 @@ pub struct ProfileContext { pub name: Option, pub description: Option, pub image: Option, + // the path to the blob defined in the `image` field (aka the profile picture) + pub blob_path: Option, } impl ProfileContext { @@ -57,6 +59,7 @@ impl ProfileContext { name: None, description: None, image: None, + blob_path: None, } } @@ -99,7 +102,14 @@ impl ProfileContext { _ => (), } } - context.id = Some(id); + + // determine the path to the blob defined by the value of `context.image` + if let Some(ref blob_id) = context.image { + context.blob_path = match sbot_client.get_blob_path(&blob_id).await { + Ok(id) => Some(id), + Err(_) => None, + } + } } else { // the sbot is not currently active; return a helpful message context.flash_name = Some("warning".to_string()); diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index e932acf..76c6ff2 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -12,7 +12,11 @@ - Profile picture + {%- if blob_path %} + Profile picture + {% else %} + Profile picture + {% endif %}

{{ name }}

From 799d9de001bae4e955abf0d98e811aaf6488fae9 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 09:08:21 +0200 Subject: [PATCH 11/33] add io error variant and blob writer function --- peach-web/src/error.rs | 11 ++++++++++ peach-web/src/utils.rs | 47 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/peach-web/src/error.rs b/peach-web/src/error.rs index e20608d..0d5453b 100644 --- a/peach-web/src/error.rs +++ b/peach-web/src/error.rs @@ -1,5 +1,7 @@ //! Custom error type representing all possible error variants for peach-web. +use std::io::Error as IoError; + use golgi::GolgiError; use peach_lib::error::PeachError; use peach_lib::{serde_json, serde_yaml}; @@ -12,6 +14,7 @@ pub enum PeachWebError { FailedToRegisterDynDomain(String), Golgi(GolgiError), HomeDir, + Io(IoError), Json(JsonError), OsString, PeachLib { source: PeachError, msg: String }, @@ -24,6 +27,7 @@ impl std::error::Error for PeachWebError { PeachWebError::FailedToRegisterDynDomain(_) => None, PeachWebError::Golgi(ref source) => Some(source), PeachWebError::HomeDir => None, + PeachWebError::Io(ref source) => Some(source), PeachWebError::Json(ref source) => Some(source), PeachWebError::OsString => None, PeachWebError::PeachLib { ref source, .. } => Some(source), @@ -43,6 +47,7 @@ impl std::fmt::Display for PeachWebError { f, "Filesystem error: failed to determine home directory path" ), + PeachWebError::Io(ref source) => write!(f, "IO error: {}", source), PeachWebError::Json(ref source) => write!(f, "Serde JSON error: {}", source), PeachWebError::OsString => write!( f, @@ -60,6 +65,12 @@ impl From for PeachWebError { } } +impl From for PeachWebError { + fn from(err: IoError) -> PeachWebError { + PeachWebError::Io(err) + } +} + impl From for PeachWebError { fn from(err: JsonError) -> PeachWebError { PeachWebError::Json(err) diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index dea8150..512fe77 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,11 +1,19 @@ pub mod monitor; +use std::io::prelude::*; +use std::{fs, fs::File}; + use dirs; +use golgi::blobs; use log::info; use peach_lib::sbot::SbotConfig; -use rocket::response::{Redirect, Responder}; -use rocket::serde::Serialize; +use rocket::{ + fs::TempFile, + response::{Redirect, Responder}, + serde::Serialize, +}; use rocket_dyn_templates::Template; +use temporary::Directory; use crate::{error::PeachWebError, THEME}; @@ -32,6 +40,41 @@ pub fn get_go_ssb_path() -> Result { Ok(go_ssb_path) } +// take the path to a file, add it to the blobstore and return the blob id +pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { + // create temporary directory and path + let temp_dir = Directory::new("blob")?; + // we performed a `file.name().is_some()` check before calling `write_blob_to_store` + // so it should be safe to do a simple unwrap here + let filename = file.name().expect("retrieving filename from uploaded file"); + let temp_path = temp_dir.join(filename); + + // write file to temporary path + file.persist_to(&temp_path).await?; + + // open the file and read it into a buffer + let mut file = File::open(&temp_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + + // hash the bytes representing the file + let (hex_hash, blob_id) = blobs::hash_blob(&buffer)?; + + // define the blobstore path and blob filename + let (blob_dir, blob_filename) = hex_hash.split_at(2); + let go_ssb_path = get_go_ssb_path()?; + let blobstore_sub_dir = format!("{}/blobs/sha256/{}", go_ssb_path, blob_dir); + + // create the blobstore sub-directory + fs::create_dir_all(&blobstore_sub_dir)?; + + // copy the file to the blobstore + let blob_path = format!("{}/{}", blobstore_sub_dir, blob_filename); + fs::copy(temp_path, blob_path)?; + + Ok(blob_id) +} + // THEME FUNCTIONS #[derive(Debug, Copy, Clone)] From e3eb3be2e3c96606f97951ffdf6a173493ca9f59 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 09:08:48 +0200 Subject: [PATCH 12/33] use local blobstore path util function --- peach-web/src/context/sbot.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index f8f9b0d..ab2c497 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -1,4 +1,4 @@ -use golgi::Sbot; +use golgi::{blobs, Sbot}; use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::serde::Serialize; @@ -105,7 +105,7 @@ impl ProfileContext { // determine the path to the blob defined by the value of `context.image` if let Some(ref blob_id) = context.image { - context.blob_path = match sbot_client.get_blob_path(&blob_id).await { + context.blob_path = match blobs::get_blob_path(&blob_id) { Ok(id) => Some(id), Err(_) => None, } From ebbcc35fbb05aaa6234b6c1f7e0c97c6792c4ca7 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 09:09:07 +0200 Subject: [PATCH 13/33] add deps for base64 and temp dir creation --- peach-web/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/peach-web/Cargo.toml b/peach-web/Cargo.toml index 74ba710..7ba1a43 100644 --- a/peach-web/Cargo.toml +++ b/peach-web/Cargo.toml @@ -35,6 +35,7 @@ travis-ci = { repository = "peachcloud/peach-web", branch = "master" } maintenance = { status = "actively-developed" } [dependencies] +base64 = "0.13.0" dirs = "4.0.0" env_logger = "0.8" #golgi = "0.1.0" @@ -48,6 +49,7 @@ peach-stats = { path = "../peach-stats", features = ["serde_support"] } rocket = { version = "0.5.0-rc.1", features = ["json", "secrets"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +temporary = "0.6.4" tera = { version = "1.12.1", features = ["builtins"] } xdg = "2.2.0" From 6d2502257def1f9ba66fdfd9ceb7e1b31b173257 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 09:09:41 +0200 Subject: [PATCH 14/33] add profile image uploader and blob saver --- peach-web/src/routes/scuttlebutt.rs | 67 ++++++++++++++----- .../scuttlebutt/update_profile.html.tera | 7 +- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 5188bd3..120e85e 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -4,9 +4,8 @@ use log::debug; use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{ form::{Form, FromForm}, - get, - http::RawStr, - post, + fs::TempFile, + get, post, request::FlashMessage, response::{Flash, Redirect}, serde::{Deserialize, Serialize}, @@ -346,8 +345,8 @@ pub async fn profile( // HELPERS AND ROUTES FOR /profile/update -/// Serve a form for the purpose of updating the name and description for the -/// local Scuttlebutt profile. +/// Serve a form for the purpose of updating the name, description and picture +/// for the local Scuttlebutt profile. #[get("/profile/update")] pub async fn update_profile(flash: Option>, _auth: Authenticated) -> Template { // build the profile context object @@ -379,20 +378,25 @@ pub async fn update_profile(flash: Option>, _auth: Authenticate } } -#[derive(Debug, Deserialize, FromForm)] -pub struct Profile { +#[derive(Debug, FromForm)] +pub struct Profile<'f> { + pub id: String, pub current_name: String, pub current_description: String, pub new_name: String, pub new_description: String, + pub image: TempFile<'f>, } -/// Update the name and description for the local Scuttlebutt profile. +/// Update the name, description and picture for the local Scuttlebutt profile. /// Redirects to profile page of the PeachCloud local identity with a flash /// message describing the outcome of the action (may be successful or /// unsuccessful). #[post("/profile/update", data = "")] -pub async fn update_profile_post(profile: Form, _auth: Authenticated) -> Flash { +pub async fn update_profile_post( + mut profile: Form>, + _auth: Authenticated, +) -> Flash { let url = uri!("/scuttlebutt/profile/update"); // retrieve go-sbot systemd process status @@ -404,12 +408,14 @@ pub async fn update_profile_post(profile: Form, _auth: Authenticated) - // retrieve latest go-sbot configuration parameters let sbot_config = SbotConfig::read().ok(); - // allows us to track whether the name and / or description have been updated - let mut updated: bool = false; - // initialise sbot connection with ip:port and shscap from config file match sbot::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { + // track whether the name, description or image have been updated + let mut name_updated: bool = false; + let mut description_updated: bool = false; + let image_updated: bool; + // only update the name if it has changed if profile.new_name != profile.current_name { debug!("Publishing new Scuttlebutt profile name"); @@ -420,7 +426,7 @@ pub async fn update_profile_post(profile: Form, _auth: Authenticated) - format!("Failed to update name: {}", publish_name_res.unwrap_err()), ); } else { - updated = true + name_updated = true } } @@ -440,11 +446,42 @@ pub async fn update_profile_post(profile: Form, _auth: Authenticated) - ), ); } else { - updated = true + description_updated = true } } - if updated { + // only update the image if a file was uploaded + if profile.image.name().is_some() { + match utils::write_blob_to_store(&mut profile.image).await { + Ok(blob_id) => { + // if the file was successfully added to the blobstore, + // publish an about image message with the blob id + let publish_image_res = sbot_client.publish_image(&blob_id).await; + + if publish_image_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update image: {}", + publish_image_res.unwrap_err() + ), + ); + } else { + image_updated = true + } + } + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to add image to blobstore: {}", e), + ) + } + } + } else { + image_updated = false + } + + if name_updated || description_updated || image_updated { return Flash::success(Redirect::to(url), "Profile updated"); } else { // no updates were made but no errors were encountered either diff --git a/peach-web/templates/scuttlebutt/update_profile.html.tera b/peach-web/templates/scuttlebutt/update_profile.html.tera index 15114c3..00c5199 100644 --- a/peach-web/templates/scuttlebutt/update_profile.html.tera +++ b/peach-web/templates/scuttlebutt/update_profile.html.tera @@ -4,16 +4,19 @@ {# ---------------- #}
-
+
+ +
+ -
+ From 02a1078eceb527dccf8154458edf2b956d416982 Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 09:10:09 +0200 Subject: [PATCH 15/33] make profile capsule border pink --- peach-web/templates/scuttlebutt/profile.html.tera | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 76c6ff2..7ac52a8 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -5,7 +5,7 @@ {# only render the profile info elements is the sbot is active #} {%- if sbot_status and sbot_status.state == "active" %} -
+
Edit From a491892bd9e6dd1b693d7a28eca90d3c8d15ce9b Mon Sep 17 00:00:00 2001 From: glyph Date: Fri, 25 Feb 2022 10:49:37 +0200 Subject: [PATCH 16/33] assign public key id to profile context --- peach-web/src/context/sbot.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index ab2c497..b55b78b 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -103,6 +103,10 @@ impl ProfileContext { } } + // assign the ssb public key to the context + // (could be for the local profile or a peer) + context.id = Some(id); + // determine the path to the blob defined by the value of `context.image` if let Some(ref blob_id) = context.image { context.blob_path = match blobs::get_blob_path(&blob_id) { From 786e3f41d93b13255439ef3863423b677dc3e37f Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 28 Feb 2022 16:05:53 +0200 Subject: [PATCH 17/33] add utility to check blobstore for file --- peach-web/src/utils.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/peach-web/src/utils.rs b/peach-web/src/utils.rs index 512fe77..5b85099 100644 --- a/peach-web/src/utils.rs +++ b/peach-web/src/utils.rs @@ -1,7 +1,7 @@ pub mod monitor; use std::io::prelude::*; -use std::{fs, fs::File}; +use std::{fs, fs::File, path::Path}; use dirs; use golgi::blobs; @@ -40,6 +40,15 @@ pub fn get_go_ssb_path() -> Result { Ok(go_ssb_path) } +// check whether a blob is in the blobstore +pub async fn blob_is_stored_locally(blob_path: &str) -> Result { + let go_ssb_path = get_go_ssb_path()?; + let complete_path = format!("{}/blobs/sha256/{}", go_ssb_path, blob_path); + let blob_exists_locally = Path::new(&complete_path).exists(); + + Ok(blob_exists_locally) +} + // take the path to a file, add it to the blobstore and return the blob id pub async fn write_blob_to_store(file: &mut TempFile<'_>) -> Result { // create temporary directory and path From 436a516c3e023cde6a34cbfc30636d876c01aad1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 28 Feb 2022 16:06:52 +0200 Subject: [PATCH 18/33] add conditional rendering for profile and peers list --- peach-web/src/context/sbot.rs | 155 +++++++++++++++++- .../scuttlebutt/peers_list.html.tera | 20 ++- .../templates/scuttlebutt/profile.html.tera | 20 ++- 3 files changed, 183 insertions(+), 12 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index b55b78b..7f177b3 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -1,4 +1,6 @@ -use golgi::{blobs, Sbot}; +use std::collections::HashMap; + +use golgi::{api::friends::RelationshipQuery, blobs, Sbot}; use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::serde::Serialize; @@ -24,6 +26,97 @@ pub async fn init_sbot_with_config( // CONTEXT STRUCTS AND BUILDERS +// peers who follow the local account +#[derive(Debug, Serialize)] +pub struct FollowsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FollowsContext { + pub fn default() -> Self { + FollowsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Follows".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + #[derive(Debug, Serialize)] pub struct ProfileContext { pub back: Option, @@ -42,6 +135,11 @@ pub struct ProfileContext { pub image: Option, // the path to the blob defined in the `image` field (aka the profile picture) pub blob_path: Option, + // whether or not the blob exists in the blobstore (ie. is saved on disk) + pub blob_exists: bool, + // relationship state (if the profile being viewed is not for the local public key) + pub following: Option, + pub blocking: Option, } impl ProfileContext { @@ -60,6 +158,9 @@ impl ProfileContext { description: None, image: None, blob_path: None, + blob_exists: false, + following: None, + blocking: None, } } @@ -79,18 +180,56 @@ impl ProfileContext { let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + let local_id = sbot_client.whoami().await?; + // if an ssb_id has been provided to the context builder, we assume that // the profile info being retrieved is for a peer (ie. not for our local // profile) let id = if ssb_id.is_some() { + // we are not dealing with the local profile context.is_local_profile = false; - ssb_id.unwrap() + + // we're safe to unwrap here because we know it's `Some(id)` + let peer_id = ssb_id.unwrap(); + + // determine relationship between peer and local id + let follow_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query follow state + context.following = match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => Some(true), + Ok(following) if following == "false" => Some(false), + _ => None, + }; + + // TODO: i don't like that we have to instantiate the same query object + // twice. see if we can streamline this in golgi + let block_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query block state + context.blocking = match sbot_client.friends_is_blocking(block_query).await { + Ok(blocking) if blocking == "true" => Some(true), + Ok(blocking) if blocking == "false" => Some(false), + _ => None, + }; + + peer_id } else { // if an ssb_id has not been provided, retrieve the local id using whoami context.is_local_profile = true; - sbot_client.whoami().await? + + local_id }; + // TODO: add relationship state context if not local profile + // ie. lookup is_following and is_blocking, set context accordingly + // retrieve the profile info for the given id let info = sbot_client.get_profile_info(&id).await?; // set each context field accordingly @@ -110,7 +249,15 @@ impl ProfileContext { // determine the path to the blob defined by the value of `context.image` if let Some(ref blob_id) = context.image { context.blob_path = match blobs::get_blob_path(&blob_id) { - Ok(id) => Some(id), + Ok(path) => { + // if we get the path, check if the blob is in the blobstore. + // this allows us to default to a placeholder image in the template + if let Ok(exists) = utils::blob_is_stored_locally(&path).await { + context.blob_exists = exists + }; + + Some(path) + } Err(_) => None, } } diff --git a/peach-web/templates/scuttlebutt/peers_list.html.tera b/peach-web/templates/scuttlebutt/peers_list.html.tera index 2b61d78..c2dcfb4 100644 --- a/peach-web/templates/scuttlebutt/peers_list.html.tera +++ b/peach-web/templates/scuttlebutt/peers_list.html.tera @@ -1,16 +1,24 @@ {%- extends "nav" -%} {%- block card %} {%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 7ac52a8..de40580 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -6,15 +6,19 @@ {%- if sbot_status and sbot_status.state == "active" %}
+ {% if is_local_profile %} Edit + {% endif %} - {%- if blob_path %} + {# only try to render profile pic if we have the blob #} + {%- if blob_path and blob_exists == true %} Profile picture {% else %} + {# render a placeholder profile picture (icon) #} Profile picture {% endif %} @@ -33,9 +37,21 @@
+ {% if following == false %} Follow + {% elif following == true %} + Unfollow + {% else %} +

Unable to determine follow state

+ {% endif %} + {% if blocking == false %} Block - Private Message + {% elif blocking == true %} + Unblock + {% else %} +

Unable to determine block state

+ {% endif %} + Send Private Message
{%- endif %} {%- endif %} From e10468c337b85e3cd2096d9827918c0da9acfb97 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 28 Feb 2022 16:08:05 +0200 Subject: [PATCH 19/33] add basic peer lookup, improve follows and profile route handlers --- peach-web/src/router.rs | 1 + peach-web/src/routes/scuttlebutt.rs | 102 +++++++++++------- .../scuttlebutt/peer_lookup.html.tera | 26 +++++ .../templates/scuttlebutt/peers.html.tera | 2 + 4 files changed, 90 insertions(+), 41 deletions(-) create mode 100644 peach-web/templates/scuttlebutt/peer_lookup.html.tera diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index fd990d4..4664bd1 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -72,6 +72,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { invites, create_invite, peers, + peer_lookup, friends, follows, followers, diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 120e85e..d3cb906 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -14,7 +14,10 @@ use rocket::{ use rocket_dyn_templates::{tera::Context, Template}; use crate::{ - context::{sbot, sbot::ProfileContext}, + context::{ + sbot, + sbot::{FollowsContext, ProfileContext}, + }, routes::authentication::Authenticated, utils, }; @@ -141,6 +144,32 @@ pub fn private(flash: Option, _auth: Authenticated) -> Template { Template::render("scuttlebutt/messages", &context) } +// HELPERS AND ROUTES FOR /peer/lookup + +/// Lookup and define peer relationships. +#[get("/peer/lookup")] +pub fn peer_lookup(flash: Option, _auth: Authenticated) -> Template { + // retrieve current ui theme + let theme = utils::get_theme(); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read().ok(); + + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("sbot_status", &sbot_status); + context.insert("title", &Some("Peer Lookup")); + + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); + }; + + Template::render("scuttlebutt/peer_lookup", &context.into_json()) +} + // HELPERS AND ROUTES FOR /peers #[derive(Debug, Serialize)] @@ -301,16 +330,19 @@ pub fn block(pub_key: Form, _auth: Authenticated) -> Flash /// or the profile of a peer. If the public key query parameter is not provided, /// the local profile is displayed (ie. the profile of the public key associated /// with the local PeachCloud device). -// TODO: this query param approach is not going to work because some characters -// get escaped. maybe we can use a RawString type? -#[get("/profile?")] +#[get("/profile?")] pub async fn profile( - pub_key: Option, + mut public_key: Option, flash: Option>, _auth: Authenticated, ) -> Template { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); + } + // build the profile context object - let context = ProfileContext::build(pub_key).await; + let context = ProfileContext::build(public_key).await; match context { // we were able to build the context without errors @@ -548,47 +580,35 @@ pub fn friends(flash: Option, _auth: Authenticated) -> Template { // HELPERS AND ROUTES FOR /follows -#[derive(Debug, Serialize)] -pub struct FollowsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl FollowsContext { - pub fn build() -> FollowsContext { - FollowsContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - /// A list of follows (peers we follow who do not follow us), with each list item displaying the name, image and public /// key of the peer. #[get("/follows")] -pub fn follows(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +pub async fn follows(flash: Option>, _auth: Authenticated) -> Template { + // build the follows context object + let context = FollowsContext::build().await; - let mut context = FollowsContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Follows".to_string()); - context.theme = Some(theme); + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = FollowsContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); - Template::render("scuttlebutt/peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) + } + } } // HELPERS AND ROUTES FOR /followers diff --git a/peach-web/templates/scuttlebutt/peer_lookup.html.tera b/peach-web/templates/scuttlebutt/peer_lookup.html.tera new file mode 100644 index 0000000..1bb4d7a --- /dev/null +++ b/peach-web/templates/scuttlebutt/peer_lookup.html.tera @@ -0,0 +1,26 @@ +{%- extends "nav" -%} +{%- block card %} + {# ASSIGN VARIABLES #} + {# ---------------- #} + +
+ +
+ + +
+ +
+ +
+ +
+
+ + + + + + {% include "snippets/flash_message" %} +
+{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index 74d7284..337395f 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -5,6 +5,8 @@
+ + Peer Lookup Friends Follows Followers From 03028a2278c23b932e45bf9019cb28a825a54413 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 28 Feb 2022 16:25:06 +0200 Subject: [PATCH 20/33] add context builder for friends list --- peach-web/src/context/sbot.rs | 106 ++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index 7f177b3..92e912f 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -117,6 +117,112 @@ impl FollowsContext { } } +// peers who follow and are followed by the local account (friends) +#[derive(Debug, Serialize)] +pub struct FriendsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FriendsContext { + pub fn default() -> Self { + FriendsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Friends".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let peer_id = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&peer_id).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), peer_id.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + + // check if the peer follows us (making us friends) + let follow_query = RelationshipQuery { + source: peer_id.to_string(), + dest: local_id.clone(), + }; + + // query follow state + match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => { + // only push profile info to peer_list vec if they follow us + peer_info.push(info) + } + _ => (), + }; + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + #[derive(Debug, Serialize)] pub struct ProfileContext { pub back: Option, From 814162ce7d00ce4827ccb2b09a95d6b9053c1be3 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 28 Feb 2022 16:25:31 +0200 Subject: [PATCH 21/33] update friends list handler and remove followers --- peach-web/src/router.rs | 1 - peach-web/src/routes/scuttlebutt.rs | 105 ++++-------------- .../templates/scuttlebutt/peers.html.tera | 1 - 3 files changed, 24 insertions(+), 83 deletions(-) diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 4664bd1..f8c6e6d 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -75,7 +75,6 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { peer_lookup, friends, follows, - followers, blocks, profile, update_profile, diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index d3cb906..2a7d102 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -16,7 +16,7 @@ use rocket_dyn_templates::{tera::Context, Template}; use crate::{ context::{ sbot, - sbot::{FollowsContext, ProfileContext}, + sbot::{FollowsContext, FriendsContext, ProfileContext}, }, routes::authentication::Authenticated, utils, @@ -535,47 +535,35 @@ pub async fn update_profile_post( // HELPERS AND ROUTES FOR /friends -#[derive(Debug, Serialize)] -pub struct FriendsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl FriendsContext { - pub fn build() -> FriendsContext { - FriendsContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - /// A list of friends (mutual follows), with each list item displaying the /// name, image and public key of the peer. #[get("/friends")] -pub fn friends(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +pub async fn friends(flash: Option>, _auth: Authenticated) -> Template { + // build the friends context object + let context = FriendsContext::build().await; - let mut context = FriendsContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Friends".to_string()); - context.theme = Some(theme); + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = FriendsContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); - Template::render("scuttlebutt/peers_list", &context) + Template::render("scuttlebutt/peers_list", &context) + } + } } // HELPERS AND ROUTES FOR /follows @@ -611,51 +599,6 @@ pub async fn follows(flash: Option>, _auth: Authenticated) -> T } } -// HELPERS AND ROUTES FOR /followers - -#[derive(Debug, Serialize)] -pub struct FollowersContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl FollowersContext { - pub fn build() -> FollowersContext { - FollowersContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - -/// A list of followers (peers who follow us but who we do not follow), with each list item displaying the name, image and public -/// key of the peer. -#[get("/followers")] -pub fn followers(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = FollowersContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Followers".to_string()); - context.theme = Some(theme); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) -} - // HELPERS AND ROUTES FOR /blocks #[derive(Debug, Serialize)] diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index 337395f..628ec91 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -9,7 +9,6 @@ Peer Lookup Friends Follows - Followers Blocks Invites
From a38394054d3edbe2c688b4c4c7c225bf30944585 Mon Sep 17 00:00:00 2001 From: glyph Date: Tue, 1 Mar 2022 10:53:45 +0200 Subject: [PATCH 22/33] implement peer search with key validation --- peach-web/src/router.rs | 3 +- peach-web/src/routes/scuttlebutt.rs | 131 ++++++++++++------ .../scuttlebutt/peer_lookup.html.tera | 26 ---- .../templates/scuttlebutt/peers.html.tera | 11 +- .../templates/scuttlebutt/search.html.tera | 18 +++ 5 files changed, 112 insertions(+), 77 deletions(-) delete mode 100644 peach-web/templates/scuttlebutt/peer_lookup.html.tera create mode 100644 peach-web/templates/scuttlebutt/search.html.tera diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index f8c6e6d..9ad2ae6 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -72,7 +72,8 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { invites, create_invite, peers, - peer_lookup, + search, + search_post, friends, follows, blocks, diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 2a7d102..005817d 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -8,7 +8,7 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::{Deserialize, Serialize}, + serde::Serialize, uri, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -54,7 +54,7 @@ pub fn invites(flash: Option, _auth: Authenticated) -> Template { Template::render("scuttlebutt/invites", &context.into_json()) } -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct Invite { pub uses: u16, } @@ -63,7 +63,7 @@ pub struct Invite { pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash { let uses = invite.uses; - let url = uri!("/scuttlebutt/invites"); + let url = uri!("/scuttlebutt", invites); // retrieve go-sbot systemd process status // TODO: handle unwrap properly @@ -144,11 +144,11 @@ pub fn private(flash: Option, _auth: Authenticated) -> Template { Template::render("scuttlebutt/messages", &context) } -// HELPERS AND ROUTES FOR /peer/lookup +// HELPERS AND ROUTES FOR /search -/// Lookup and define peer relationships. -#[get("/peer/lookup")] -pub fn peer_lookup(flash: Option, _auth: Authenticated) -> Template { +/// Search for a peer. +#[get("/search")] +pub fn search(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme let theme = utils::get_theme(); @@ -158,7 +158,8 @@ pub fn peer_lookup(flash: Option, _auth: Authenticated) -> Templat let mut context = Context::new(); context.insert("theme", &theme); context.insert("sbot_status", &sbot_status); - context.insert("title", &Some("Peer Lookup")); + context.insert("title", &Some("Search")); + context.insert("back", &Some("/scuttlebutt/peers")); // check to see if there is a flash message to display if let Some(flash) = flash { @@ -167,32 +168,74 @@ pub fn peer_lookup(flash: Option, _auth: Authenticated) -> Templat context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("scuttlebutt/peer_lookup", &context.into_json()) + Template::render("scuttlebutt/search", &context.into_json()) +} + +#[derive(Debug, FromForm)] +pub struct Search { + pub public_key: String, +} + +/// Accept the peer search form and redirect to the profile for that peer. +#[post("/search", data = "")] +pub async fn search_post(search: Form, _auth: Authenticated) -> Flash { + let public_key = &search.public_key; + + let search_url = "/scuttlebutt/search"; + + // validate the key before redirecting to profile url + + // ensure the id starts with the correct sigil link + if !public_key.starts_with('@') { + return Flash::error( + Redirect::to(search_url), + "Invalid key: expected '@' sigil as first character", + ); + } + + // find the dot index denoting the start of the algorithm definition tag + let dot_index = match public_key.rfind('.') { + Some(index) => index, + None => { + return Flash::error( + Redirect::to(search_url), + "Invalid key: no dot index was found", + ) + } + }; + + // check hashing algorithm (must end with ".ed25519") + if !&public_key.ends_with(".ed25519") { + return Flash::error( + Redirect::to(search_url), + "Invalid key: hashing algorithm must be ed25519", + ); + } + + // obtain the base64 portion (substring) of the public key + let base64_str = &public_key[1..dot_index]; + + // length of a base64 encoded ed25519 public key + if !base64_str.len() == 44 { + return Flash::error( + Redirect::to(search_url), + "Invalid key: base64 data length is incorrect", + ); + } + + // key has not been validated and we can redirect to the profile page + let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); + + Flash::new( + Redirect::to(profile_url), + // this flash msg will not be displayed in the receiving template + "ignore", + "Public key validated for profile lookup", + ) } // HELPERS AND ROUTES FOR /peers -#[derive(Debug, Serialize)] -pub struct PeerContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} - -impl PeerContext { - pub fn build() -> PeerContext { - PeerContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, - } - } -} - /// A peer menu which allows navigating to lists of friends, follows, followers /// and blocks, as well as accessing the invite creation form. #[get("/peers")] @@ -200,24 +243,24 @@ pub fn peers(flash: Option, _auth: Authenticated) -> Template { // retrieve current ui theme let theme = utils::get_theme(); - let mut context = PeerContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Scuttlebutt Peers".to_string()); - context.theme = Some(theme); + let mut context = Context::new(); + context.insert("theme", &theme); + context.insert("title", &Some("Scuttlebutt Peers")); + context.insert("back", &Some("/")); // check to see if there is a flash message to display if let Some(flash) = flash { // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + context.insert("flash_name", &Some(flash.kind().to_string())); + context.insert("flash_msg", &Some(flash.message().to_string())); }; - Template::render("scuttlebutt/peers", &context) + Template::render("scuttlebutt/peers", &context.into_json()) } // HELPERS AND ROUTES FOR /post/publish -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct Post { pub text: String, } @@ -230,7 +273,7 @@ pub struct Post { pub async fn publish(post: Form, _auth: Authenticated) -> Flash { let post_text = &post.text; - let url = uri!("/scuttlebutt/profile"); + let url = uri!("/scuttlebutt", profile(None::)); // retrieve go-sbot systemd process status // TODO: handle unwrap properly @@ -267,7 +310,7 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash // HELPERS AND ROUTES FOR /follow -#[derive(Debug, Deserialize, FromForm)] +#[derive(Debug, FromForm)] pub struct PublicKey { pub key: String, } @@ -282,7 +325,7 @@ pub fn follow(pub_key: Form, _auth: Authenticated) -> Flash // if successful, redirect to profile page with provided public key and flash "success" // if error, redirect to profile page with provided public key and flash "error" // redirect to the profile template with provided public key - let profile_url = uri!(profile(Some(public_key))); + let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); let success_msg = format!("Followed {}", public_key); Flash::success(Redirect::to(profile_url), success_msg) @@ -300,7 +343,7 @@ pub fn unfollow(pub_key: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash // if successful, redirect to profile page with provided public key and flash "success" // if error, redirect to profile page with provided public key and flash "error" // redirect to the profile template with provided public key - let profile_url = uri!(profile(Some(public_key))); + let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); let success_msg = format!("Blocked {}", public_key); Flash::success(Redirect::to(profile_url), success_msg) @@ -429,7 +472,7 @@ pub async fn update_profile_post( mut profile: Form>, _auth: Authenticated, ) -> Flash { - let url = uri!("/scuttlebutt/profile/update"); + let url = uri!("/scuttlebutt", update_profile); // retrieve go-sbot systemd process status // TODO: handle unwrap properly diff --git a/peach-web/templates/scuttlebutt/peer_lookup.html.tera b/peach-web/templates/scuttlebutt/peer_lookup.html.tera deleted file mode 100644 index 1bb4d7a..0000000 --- a/peach-web/templates/scuttlebutt/peer_lookup.html.tera +++ /dev/null @@ -1,26 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - {# ASSIGN VARIABLES #} - {# ---------------- #} - -
-
-
- - -
- -
- -
- -
-
- - - -
- - {% include "snippets/flash_message" %} -
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/peers.html.tera b/peach-web/templates/scuttlebutt/peers.html.tera index 628ec91..23d667f 100644 --- a/peach-web/templates/scuttlebutt/peers.html.tera +++ b/peach-web/templates/scuttlebutt/peers.html.tera @@ -5,12 +5,11 @@
diff --git a/peach-web/templates/scuttlebutt/search.html.tera b/peach-web/templates/scuttlebutt/search.html.tera new file mode 100644 index 0000000..e546737 --- /dev/null +++ b/peach-web/templates/scuttlebutt/search.html.tera @@ -0,0 +1,18 @@ +{%- extends "nav" -%} +{%- block card %} + {# ASSIGN VARIABLES #} + {# ---------------- #} + +
+
+
+ + +
+ + +
+ + {% include "snippets/flash_message" %} +
+{%- endblock card -%} From 020d18731b02fa42b867fca234cf5e4e36fc4815 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 08:58:54 +0200 Subject: [PATCH 23/33] add block list and implement (un)follow and (un)block --- peach-web/src/context/sbot.rs | 97 ++++- peach-web/src/router.rs | 1 + peach-web/src/routes/scuttlebutt.rs | 348 +++++++++++++----- .../templates/scuttlebutt/profile.html.tera | 20 +- .../templates/scuttlebutt/search.html.tera | 4 +- 5 files changed, 360 insertions(+), 110 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index 92e912f..ac40d85 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -26,7 +26,98 @@ pub async fn init_sbot_with_config( // CONTEXT STRUCTS AND BUILDERS -// peers who follow the local account +// peers who are blocked by the local account +#[derive(Debug, Serialize)] +pub struct BlocksContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl BlocksContext { + pub fn default() -> Self { + BlocksContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Blocks".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let blocks = sbot_client.get_blocks().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !blocks.is_empty() { + for peer in blocks.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who are followed by the local account #[derive(Debug, Serialize)] pub struct FollowsContext { pub back: Option, @@ -108,7 +199,7 @@ impl FollowsContext { } else { // the sbot is not currently active; return a helpful message context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); } context.sbot_status = Some(sbot_status); @@ -214,7 +305,7 @@ impl FriendsContext { } else { // the sbot is not currently active; return a helpful message context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); } context.sbot_status = Some(sbot_status); diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 9ad2ae6..8e9eb75 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -84,6 +84,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { follow, unfollow, block, + unblock, publish, ], ) diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 005817d..462edcf 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -16,7 +16,7 @@ use rocket_dyn_templates::{tera::Context, Template}; use crate::{ context::{ sbot, - sbot::{FollowsContext, FriendsContext, ProfileContext}, + sbot::{BlocksContext, FollowsContext, FriendsContext, ProfileContext}, }, routes::authentication::Authenticated, utils, @@ -141,7 +141,7 @@ pub fn private(flash: Option, _auth: Authenticated) -> Template { context.flash_msg = Some(flash.message().to_string()); }; - Template::render("scuttlebutt/messages", &context) + Template::render("scuttlebutt/private", &context) } // HELPERS AND ROUTES FOR /search @@ -172,55 +172,63 @@ pub fn search(flash: Option, _auth: Authenticated) -> Template { } #[derive(Debug, FromForm)] -pub struct Search { +pub struct Peer { + // public key pub public_key: String, } -/// Accept the peer search form and redirect to the profile for that peer. -#[post("/search", data = "")] -pub async fn search_post(search: Form, _auth: Authenticated) -> Flash { - let public_key = &search.public_key; - - let search_url = "/scuttlebutt/search"; - - // validate the key before redirecting to profile url - +fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash> { // ensure the id starts with the correct sigil link if !public_key.starts_with('@') { - return Flash::error( - Redirect::to(search_url), + return Err(Flash::error( + Redirect::to(redirect_url), "Invalid key: expected '@' sigil as first character", - ); + )); } // find the dot index denoting the start of the algorithm definition tag let dot_index = match public_key.rfind('.') { Some(index) => index, None => { - return Flash::error( - Redirect::to(search_url), + return Err(Flash::error( + Redirect::to(redirect_url), "Invalid key: no dot index was found", - ) + )) } }; // check hashing algorithm (must end with ".ed25519") if !&public_key.ends_with(".ed25519") { - return Flash::error( - Redirect::to(search_url), + return Err(Flash::error( + Redirect::to(redirect_url), "Invalid key: hashing algorithm must be ed25519", - ); + )); } // obtain the base64 portion (substring) of the public key let base64_str = &public_key[1..dot_index]; // length of a base64 encoded ed25519 public key - if !base64_str.len() == 44 { - return Flash::error( - Redirect::to(search_url), + if base64_str.len() != 44 { + return Err(Flash::error( + Redirect::to(redirect_url), "Invalid key: base64 data length is incorrect", - ); + )); + } + + Ok(()) +} + +/// Accept the peer search form and redirect to the profile for that peer. +#[post("/search", data = "")] +pub async fn search_post(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + + let search_url = "/scuttlebutt/search".to_string(); + + // validate the key before redirecting to profile url + if let Err(flash) = validate_public_key(&public_key, search_url) { + return flash; } // key has not been validated and we can redirect to the profile page @@ -276,8 +284,15 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash let url = uri!("/scuttlebutt", profile(None::)); // retrieve go-sbot systemd process status - // TODO: handle unwrap properly - let sbot_status = SbotStatus::read().unwrap(); + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } + }; // we only want to try and interact with the sbot if it's active if sbot_status.state == Some("active".to_string()) { @@ -310,25 +325,53 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash // HELPERS AND ROUTES FOR /follow -#[derive(Debug, FromForm)] -pub struct PublicKey { - pub key: String, -} - /// Follow a Scuttlebutt profile specified by the given public key. /// Redirects to the appropriate profile page with a flash message describing /// the outcome of the action (may be successful or unsuccessful). -#[post("/follow", data = "")] -pub fn follow(pub_key: Form, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli follow action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); - let success_msg = format!("Followed {}", public_key); +#[post("/follow", data = "")] +pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; - Flash::success(Redirect::to(profile_url), success_msg) + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } + }; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Following Scuttlebutt peer"); + match sbot_client.follow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Followed peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to follow peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // HELPERS AND ROUTES FOR /unfollow @@ -336,17 +379,50 @@ pub fn follow(pub_key: Form, _auth: Authenticated) -> Flash /// Unfollow a Scuttlebutt profile specified by the given public key. /// Redirects to the appropriate profile page with a flash message describing /// the outcome of the action (may be successful or unsuccessful). -#[post("/unfollow", data = "")] -pub fn unfollow(pub_key: Form, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli unfollow action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); - let success_msg = format!("Unfollowed {}", public_key); +#[post("/unfollow", data = "")] +pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; - Flash::success(Redirect::to(profile_url), success_msg) + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } + }; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unfollowing Scuttlebutt peer"); + match sbot_client.unfollow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unfollowed peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to unfollow peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // HELPERS AND ROUTES FOR /block @@ -354,17 +430,101 @@ pub fn unfollow(pub_key: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { - let public_key = &pub_key.key; - // perform the sbotcli block action using &pub_key.0 - // if successful, redirect to profile page with provided public key and flash "success" - // if error, redirect to profile page with provided public key and flash "error" - // redirect to the profile template with provided public key - let profile_url = uri!("/scuttlebutt", profile(Some(public_key))); - let success_msg = format!("Blocked {}", public_key); +#[post("/block", data = "")] +pub async fn block(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; - Flash::success(Redirect::to(profile_url), success_msg) + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } + }; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Blocking Scuttlebutt peer"); + match sbot_client.block(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Blocked peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to block peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } +} + +// HELPERS AND ROUTES FOR /unblock + +/// Unblock a Scuttlebutt profile specified by the given public key. +/// Redirects to the appropriate profile page with a flash message describing +/// the outcome of the action (may be successful or unsuccessful). +#[post("/unblock", data = "")] +pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash { + let public_key = &peer.public_key; + + let url = uri!("/scuttlebutt", profile(Some(public_key))); + + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } + }; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unblocking Scuttlebutt peer"); + match sbot_client.unblock(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unblocked peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to unblock peer: {}", e)) + } + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // ROUTES FOR /profile @@ -644,45 +804,33 @@ pub async fn follows(flash: Option>, _auth: Authenticated) -> T // HELPERS AND ROUTES FOR /blocks -#[derive(Debug, Serialize)] -pub struct BlocksContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A list of blocks (peers we've blocked previously), with each list item +/// displaying the name, image and public key of the peer. +#[get("/blocks")] +pub async fn blocks(flash: Option>, _auth: Authenticated) -> Template { + // build the blocks context object + let context = BlocksContext::build().await; -impl BlocksContext { - pub fn build() -> BlocksContext { - BlocksContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/peers_list", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = BlocksContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/peers_list", &context) } } } - -/// A list of blocks (peers we've blocked previously), with each list item displaying the name, image and public -/// key of the peer. -#[get("/blocks")] -pub fn blocks(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); - - let mut context = BlocksContext::build(); - context.back = Some("/scuttlebutt/peers".to_string()); - context.title = Some("Blocks".to_string()); - context.theme = Some(theme); - - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); - }; - - Template::render("scuttlebutt/peers_list", &context) -} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index de40580..9237134 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -38,16 +38,28 @@
{% if following == false %} - Follow +
+ + +
{% elif following == true %} - Unfollow +
+ + +
{% else %}

Unable to determine follow state

{% endif %} {% if blocking == false %} - Block +
+ + +
{% elif blocking == true %} - Unblock +
+ + +
{% else %}

Unable to determine block state

{% endif %} diff --git a/peach-web/templates/scuttlebutt/search.html.tera b/peach-web/templates/scuttlebutt/search.html.tera index e546737..d5e508d 100644 --- a/peach-web/templates/scuttlebutt/search.html.tera +++ b/peach-web/templates/scuttlebutt/search.html.tera @@ -1,13 +1,11 @@ {%- extends "nav" -%} {%- block card %} - {# ASSIGN VARIABLES #} - {# ---------------- #}
- +
From 6cba477f1579d8532b41533a6fdae4fa97734286 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 11:57:10 +0200 Subject: [PATCH 24/33] add private message publishing --- peach-web/src/context/sbot.rs | 57 +++++++++ peach-web/src/router.rs | 1 + peach-web/src/routes/scuttlebutt.rs | 118 +++++++++++++----- .../templates/scuttlebutt/messages.html.tera | 10 -- .../templates/scuttlebutt/private.html.tera | 22 ++++ 5 files changed, 165 insertions(+), 43 deletions(-) delete mode 100644 peach-web/templates/scuttlebutt/messages.html.tera create mode 100644 peach-web/templates/scuttlebutt/private.html.tera diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index ac40d85..1af9eb4 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -469,3 +469,60 @@ impl ProfileContext { Ok(context) } } + +#[derive(Debug, Serialize)] +pub struct PrivateContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // local peer id (whoami) + pub id: Option, +} + +impl PrivateContext { + pub fn default() -> Self { + PrivateContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Private Messages".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + id: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + context.id = Some(local_id); + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, private messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} diff --git a/peach-web/src/router.rs b/peach-web/src/router.rs index 8e9eb75..ee43d47 100644 --- a/peach-web/src/router.rs +++ b/peach-web/src/router.rs @@ -81,6 +81,7 @@ pub fn mount_peachpub_routes(rocket: Rocket) -> Rocket { update_profile, update_profile_post, private, + private_post, follow, unfollow, block, diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 462edcf..9ed2d49 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -8,7 +8,6 @@ use rocket::{ get, post, request::FlashMessage, response::{Flash, Redirect}, - serde::Serialize, uri, }; use rocket_dyn_templates::{tera::Context, Template}; @@ -16,7 +15,7 @@ use rocket_dyn_templates::{tera::Context, Template}; use crate::{ context::{ sbot, - sbot::{BlocksContext, FollowsContext, FriendsContext, ProfileContext}, + sbot::{BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext}, }, routes::authentication::Authenticated, utils, @@ -102,46 +101,99 @@ pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash< // HELPERS AND ROUTES FOR /private -#[derive(Debug, Serialize)] -pub struct PrivateContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, -} +/// A private message composition and publication page. +#[get("/private")] +pub async fn private(flash: Option>, _auth: Authenticated) -> Template { + // build the private context object + let context = PrivateContext::build().await; -impl PrivateContext { - pub fn build() -> PrivateContext { - PrivateContext { - back: None, - flash_name: None, - flash_msg: None, - title: None, - theme: None, + match context { + // we were able to build the context without errors + Ok(mut context) => { + // check to see if there is a flash message to display + if let Some(flash) = flash { + // add flash message contents to the context object + context.flash_name = Some(flash.kind().to_string()); + context.flash_msg = Some(flash.message().to_string()); + }; + + Template::render("scuttlebutt/private", &context) + } + // an error occurred while building the context + Err(e) => { + // build the default context and pass along the error message + let mut context = PrivateContext::default(); + context.flash_name = Some("error".to_string()); + context.flash_msg = Some(e.to_string()); + + Template::render("scuttlebutt/private", &context) } } } -/// A private message composition and publication page. -#[get("/private")] -pub fn private(flash: Option, _auth: Authenticated) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +#[derive(Debug, FromForm)] +pub struct Private { + pub id: String, + pub text: String, + pub recipient: String, +} - let mut context = PrivateContext::build(); - context.back = Some("/".to_string()); - context.title = Some("Private Messages".to_string()); - context.theme = Some(theme); +/// Publish a private message. +#[post("/private", data = "")] +pub async fn private_post(private: Form, _auth: Authenticated) -> Flash { + let url = uri!("/scuttlebutt", private); - // check to see if there is a flash message to display - if let Some(flash) = flash { - // add flash message contents to the context object - context.flash_name = Some(flash.kind().to_string()); - context.flash_msg = Some(flash.message().to_string()); + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to read sbot status: {}", e), + ) + } }; - Template::render("scuttlebutt/private", &context) + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let id = &private.id; + let text = &private.text; + let recipient = &private.recipient; + // now we need to add the local id to the recipients vector, + // otherwise the local id will not be able to read the message. + let recipients = vec![id.to_string(), recipient.to_string()]; + + // initialise sbot connection with ip:port and shscap from config file + match sbot::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing a new Scuttlebutt private message"); + match sbot_client + .publish_private(text.to_string(), recipients) + .await + { + Ok(_) => { + Flash::success(Redirect::to(url), format!("Published private message")) + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to publish private message: {}", e), + ), + } + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), + } + } else { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } } // HELPERS AND ROUTES FOR /search diff --git a/peach-web/templates/scuttlebutt/messages.html.tera b/peach-web/templates/scuttlebutt/messages.html.tera deleted file mode 100644 index d3da0af..0000000 --- a/peach-web/templates/scuttlebutt/messages.html.tera +++ /dev/null @@ -1,10 +0,0 @@ -{%- extends "nav" -%} -{%- block card %} - -
-
- - {% include "snippets/flash_message" %} -
-
-{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera new file mode 100644 index 0000000..b626299 --- /dev/null +++ b/peach-web/templates/scuttlebutt/private.html.tera @@ -0,0 +1,22 @@ +{%- extends "nav" -%} +{%- block card %} + +
+ +
+
+ + +
+ + + + +
+ + + + + {% include "snippets/flash_message" %} +
+{%- endblock card -%} From 06a55ade06b90bb4ecad4c9f60290985fa7e6437 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 13:35:21 +0200 Subject: [PATCH 25/33] remove blob rendering for blocked peers --- peach-web/src/context/sbot.rs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index 1af9eb4..2bb5b63 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -83,22 +83,9 @@ impl BlocksContext { let mut info = sbot_client.get_profile_info(&key).await?; // insert the public key of the peer into the info hashmap info.insert("id".to_string(), key.to_string()); - // retrieve the profile image blob id for the given peer - if let Some(blob_id) = info.get("image") { - // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { - // insert the image blob path of the peer into the info hashmap - info.insert("blob_path".to_string(), blob_path.to_string()); - // check if the blob is in the blobstore - // set a flag in the info hashmap - match utils::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { - info.insert("blob_exists".to_string(), "true".to_string()) - } - _ => info.insert("blob_exists".to_string(), "false".to_string()), - }; - } - } + // we do not even attempt to find the blob for a blocked peer, + // since it may be vulgar to cause distress to the local peer. + info.insert("blob_exists".to_string(), "false".to_string()); // push profile info to peer_list vec peer_info.push(info) } From 9a07ab3ac0ad3a6a8ca987581c53319ed93d802e Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 13:51:02 +0200 Subject: [PATCH 26/33] add query param for private msgs --- peach-web/src/context/sbot.rs | 7 ++++++- peach-web/src/routes/scuttlebutt.rs | 17 +++++++++++++---- .../templates/scuttlebutt/private.html.tera | 4 ++-- .../templates/scuttlebutt/profile.html.tera | 2 +- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index 2bb5b63..c745501 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -468,6 +468,8 @@ pub struct PrivateContext { pub sbot_status: Option, // local peer id (whoami) pub id: Option, + // id of the peer being messaged + pub recipient_id: Option, } impl PrivateContext { @@ -481,10 +483,11 @@ impl PrivateContext { sbot_config: None, sbot_status: None, id: None, + recipient_id: None, } } - pub async fn build() -> Result { + pub async fn build(recipient_id: Option) -> Result { let mut context = Self::default(); // retrieve current ui theme @@ -500,6 +503,8 @@ impl PrivateContext { let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + context.recipient_id = recipient_id; + let local_id = sbot_client.whoami().await?; context.id = Some(local_id); } else { diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 9ed2d49..04d537c 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -102,10 +102,19 @@ pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash< // HELPERS AND ROUTES FOR /private /// A private message composition and publication page. -#[get("/private")] -pub async fn private(flash: Option>, _auth: Authenticated) -> Template { +#[get("/private?")] +pub async fn private( + mut public_key: Option, + flash: Option>, + _auth: Authenticated, +) -> Template { + if let Some(ref key) = public_key { + // `url_decode` replaces '+' with ' ', so we need to revert that + public_key = Some(key.replace(' ', "+")); + } + // build the private context object - let context = PrivateContext::build().await; + let context = PrivateContext::build(public_key).await; match context { // we were able to build the context without errors @@ -141,7 +150,7 @@ pub struct Private { /// Publish a private message. #[post("/private", data = "")] pub async fn private_post(private: Form, _auth: Authenticated) -> Flash { - let url = uri!("/scuttlebutt", private); + let url = uri!("/scuttlebutt", private(None::)); // retrieve go-sbot systemd process status let sbot_status = match SbotStatus::read() { diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera index b626299..45c6f72 100644 --- a/peach-web/templates/scuttlebutt/private.html.tera +++ b/peach-web/templates/scuttlebutt/private.html.tera @@ -6,10 +6,10 @@
- +
- +
diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index 9237134..a4cde01 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -63,7 +63,7 @@ {% else %}

Unable to determine block state

{% endif %} - Send Private Message + Send Private Message
{%- endif %} {%- endif %} From b1724f6eb4dff758fd2151e2c0d2d66f26c58227 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 15:24:05 +0200 Subject: [PATCH 27/33] update sbot status context and add latest seq num --- peach-web/src/context/sbot.rs | 72 ++++++++++++++++++- peach-web/src/routes/status/scuttlebutt.rs | 51 +++++++------ .../templates/status/scuttlebutt.html.tera | 28 ++------ 3 files changed, 98 insertions(+), 53 deletions(-) diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs index c745501..9d319a6 100644 --- a/peach-web/src/context/sbot.rs +++ b/peach-web/src/context/sbot.rs @@ -1,8 +1,8 @@ use std::collections::HashMap; -use golgi::{api::friends::RelationshipQuery, blobs, Sbot}; +use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; use peach_lib::sbot::{SbotConfig, SbotStatus}; -use rocket::serde::Serialize; +use rocket::{futures::TryStreamExt, serde::Serialize}; use crate::{error::PeachWebError, utils}; @@ -26,6 +26,74 @@ pub async fn init_sbot_with_config( // CONTEXT STRUCTS AND BUILDERS +#[derive(Debug, Serialize)] +pub struct StatusContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // latest sequence number for the local log + pub latest_seq: Option, +} + +impl StatusContext { + pub fn default() -> Self { + StatusContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Scuttlebutt Status".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + latest_seq: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // assign the sequence number of the latest msg + context.latest_seq = Some(msgs[0].sequence); + + context.sbot_config = sbot_config; + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + // peers who are blocked by the local account #[derive(Debug, Serialize)] pub struct BlocksContext { diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 99b97b9..2e9ff06 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -1,40 +1,37 @@ -use peach_lib::sbot::{SbotConfig, SbotStatus}; use rocket::{get, State}; -use rocket_dyn_templates::{tera::Context, Template}; +use rocket_dyn_templates::Template; use crate::routes::authentication::Authenticated; -use crate::utils; -use crate::RocketConfig; +use crate::{context::sbot::StatusContext, RocketConfig}; // HELPERS AND ROUTES FOR /status/scuttlebutt #[get("/scuttlebutt")] -pub fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { - // retrieve current ui theme - let theme = utils::get_theme(); +pub async fn scuttlebutt_status(_auth: Authenticated, config: &State) -> Template { + let context = StatusContext::build().await; - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read().ok(); - - // retrieve go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut context = Context::new(); - context.insert("theme", &theme); - context.insert("sbot_status", &sbot_status); - context.insert("sbot_config", &sbot_config); - context.insert("flash_name", &None::<()>); - context.insert("flash_msg", &None::<()>); - context.insert("title", &Some("Scuttlebutt Status")); - - // define back arrow url based on mode - if config.standalone_mode { + let back = if config.standalone_mode { // return to home page - context.insert("back", &Some("/")); + Some("/".to_string()) } else { // return to status menu - context.insert("back", &Some("/status")); - } + Some("/status".to_string()) + }; - Template::render("status/scuttlebutt", &context.into_json()) + match context { + Ok(mut context) => { + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + Err(e) => { + let mut context = StatusContext::default(); + + // define back arrow url based on mode + context.back = back; + + Template::render("status/scuttlebutt", &context) + } + } } diff --git a/peach-web/templates/status/scuttlebutt.html.tera b/peach-web/templates/status/scuttlebutt.html.tera index 787f8a5..998efad 100644 --- a/peach-web/templates/status/scuttlebutt.html.tera +++ b/peach-web/templates/status/scuttlebutt.html.tera @@ -52,33 +52,13 @@

-
-
- - -
-
- - -
-
- - -
-
- - +
+
+ +
-
From ffe190148d55d81487fcd120308e337a01328f55 Mon Sep 17 00:00:00 2001 From: glyph Date: Wed, 2 Mar 2022 15:24:40 +0200 Subject: [PATCH 28/33] minor css tweaks --- peach-web/static/css/peachcloud.css | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/peach-web/static/css/peachcloud.css b/peach-web/static/css/peachcloud.css index 250e1be..676d43a 100644 --- a/peach-web/static/css/peachcloud.css +++ b/peach-web/static/css/peachcloud.css @@ -277,6 +277,18 @@ body { padding-bottom: 1rem; } +.capsule-profile { + margin-left: 1rem; + margin-right: 1rem; +} + +@media only screen and (min-width: 600px) { + .capsule-profile { + margin-left: 0; + margin-right: 0; + } +} + @media only screen and (min-width: 600px) { .capsule-container { margin-left: 0; @@ -728,6 +740,7 @@ form { height: 7rem; overflow: auto; resize: vertical; + width: 100%; } .alert-input { @@ -753,7 +766,7 @@ form { font-family: var(--sans-serif); font-size: var(--font-size-7); display: block; - /* margin-bottom: 2px; */ + margin-bottom: 2px; } .label-medium { From 1479a65d591c759b7ceab4f1101ba2c286415cb1 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 3 Mar 2022 08:55:37 +0200 Subject: [PATCH 29/33] rename sbot context module to match all scuttlebutt.rs files --- peach-web/src/context/mod.rs | 2 +- peach-web/src/context/sbot.rs | 588 --------------------- peach-web/src/routes/scuttlebutt.rs | 22 +- peach-web/src/routes/status/scuttlebutt.rs | 4 +- 4 files changed, 15 insertions(+), 601 deletions(-) delete mode 100644 peach-web/src/context/sbot.rs diff --git a/peach-web/src/context/mod.rs b/peach-web/src/context/mod.rs index 691bc21..019dd88 100644 --- a/peach-web/src/context/mod.rs +++ b/peach-web/src/context/mod.rs @@ -1,3 +1,3 @@ pub mod dns; pub mod network; -pub mod sbot; +pub mod scuttlebutt; diff --git a/peach-web/src/context/sbot.rs b/peach-web/src/context/sbot.rs deleted file mode 100644 index 9d319a6..0000000 --- a/peach-web/src/context/sbot.rs +++ /dev/null @@ -1,588 +0,0 @@ -use std::collections::HashMap; - -use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; -use peach_lib::sbot::{SbotConfig, SbotStatus}; -use rocket::{futures::TryStreamExt, serde::Serialize}; - -use crate::{error::PeachWebError, utils}; - -// HELPER FUNCTIONS - -pub async fn init_sbot_with_config( - sbot_config: &Option, -) -> Result { - // initialise sbot connection with ip:port and shscap from config file - let sbot_client = match sbot_config { - // TODO: panics if we pass `Some(conf.shscap)` as second arg - Some(conf) => { - let ip_port = conf.lis.clone(); - Sbot::init(Some(ip_port), None).await? - } - None => Sbot::init(None, None).await?, - }; - - Ok(sbot_client) -} - -// CONTEXT STRUCTS AND BUILDERS - -#[derive(Debug, Serialize)] -pub struct StatusContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // latest sequence number for the local log - pub latest_seq: Option, -} - -impl StatusContext { - pub fn default() -> Self { - StatusContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Scuttlebutt Status".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - latest_seq: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - // retrieve the local id - let id = sbot_client.whoami().await?; - - let history_stream = sbot_client.create_history_stream(id).await?; - let mut msgs: Vec = history_stream.try_collect().await?; - - // reverse the list of messages so we can easily reference the latest one - msgs.reverse(); - - // assign the sequence number of the latest msg - context.latest_seq = Some(msgs[0].sequence); - - context.sbot_config = sbot_config; - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who are blocked by the local account -#[derive(Debug, Serialize)] -pub struct BlocksContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl BlocksContext { - pub fn default() -> Self { - BlocksContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Blocks".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let blocks = sbot_client.get_blocks().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !blocks.is_empty() { - for peer in blocks.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let key = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&key).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), key.to_string()); - // we do not even attempt to find the blob for a blocked peer, - // since it may be vulgar to cause distress to the local peer. - info.insert("blob_exists".to_string(), "false".to_string()); - // push profile info to peer_list vec - peer_info.push(info) - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who are followed by the local account -#[derive(Debug, Serialize)] -pub struct FollowsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl FollowsContext { - pub fn default() -> Self { - FollowsContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Follows".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let follows = sbot_client.get_follows().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !follows.is_empty() { - for peer in follows.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let key = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&key).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), key.to_string()); - // retrieve the profile image blob id for the given peer - if let Some(blob_id) = info.get("image") { - // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { - // insert the image blob path of the peer into the info hashmap - info.insert("blob_path".to_string(), blob_path.to_string()); - // check if the blob is in the blobstore - // set a flag in the info hashmap - match utils::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { - info.insert("blob_exists".to_string(), "true".to_string()) - } - _ => info.insert("blob_exists".to_string(), "false".to_string()), - }; - } - } - // push profile info to peer_list vec - peer_info.push(info) - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -// peers who follow and are followed by the local account (friends) -#[derive(Debug, Serialize)] -pub struct FriendsContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - pub peers: Option>>, -} - -impl FriendsContext { - pub fn default() -> Self { - FriendsContext { - back: Some("/scuttlebutt/peers".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Friends".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - peers: None, - } - } - - pub async fn build() -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let local_id = sbot_client.whoami().await?; - - let follows = sbot_client.get_follows().await?; - - // we'll use this to store the profile info for each peer who follows us - let mut peer_info = Vec::new(); - - if !follows.is_empty() { - for peer in follows.iter() { - // trim whitespace (including newline characters) and - // remove the inverted-commas around the id - let peer_id = peer.trim().replace('"', ""); - // retrieve the profile info for the given peer - let mut info = sbot_client.get_profile_info(&peer_id).await?; - // insert the public key of the peer into the info hashmap - info.insert("id".to_string(), peer_id.to_string()); - // retrieve the profile image blob id for the given peer - if let Some(blob_id) = info.get("image") { - // look-up the path for the image blob - if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { - // insert the image blob path of the peer into the info hashmap - info.insert("blob_path".to_string(), blob_path.to_string()); - // check if the blob is in the blobstore - // set a flag in the info hashmap - match utils::blob_is_stored_locally(&blob_path).await { - Ok(exists) if exists == true => { - info.insert("blob_exists".to_string(), "true".to_string()) - } - _ => info.insert("blob_exists".to_string(), "false".to_string()), - }; - } - } - - // check if the peer follows us (making us friends) - let follow_query = RelationshipQuery { - source: peer_id.to_string(), - dest: local_id.clone(), - }; - - // query follow state - match sbot_client.friends_is_following(follow_query).await { - Ok(following) if following == "true" => { - // only push profile info to peer_list vec if they follow us - peer_info.push(info) - } - _ => (), - }; - } - - context.peers = Some(peer_info) - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -#[derive(Debug, Serialize)] -pub struct ProfileContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // is this the local profile or the profile of a peer? - pub is_local_profile: bool, - // an ssb_id which may or may not be the local public key - pub id: Option, - pub name: Option, - pub description: Option, - pub image: Option, - // the path to the blob defined in the `image` field (aka the profile picture) - pub blob_path: Option, - // whether or not the blob exists in the blobstore (ie. is saved on disk) - pub blob_exists: bool, - // relationship state (if the profile being viewed is not for the local public key) - pub following: Option, - pub blocking: Option, -} - -impl ProfileContext { - pub fn default() -> Self { - ProfileContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Profile".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - is_local_profile: true, - id: None, - name: None, - description: None, - image: None, - blob_path: None, - blob_exists: false, - following: None, - blocking: None, - } - } - - pub async fn build(ssb_id: Option) -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - let local_id = sbot_client.whoami().await?; - - // if an ssb_id has been provided to the context builder, we assume that - // the profile info being retrieved is for a peer (ie. not for our local - // profile) - let id = if ssb_id.is_some() { - // we are not dealing with the local profile - context.is_local_profile = false; - - // we're safe to unwrap here because we know it's `Some(id)` - let peer_id = ssb_id.unwrap(); - - // determine relationship between peer and local id - let follow_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query follow state - context.following = match sbot_client.friends_is_following(follow_query).await { - Ok(following) if following == "true" => Some(true), - Ok(following) if following == "false" => Some(false), - _ => None, - }; - - // TODO: i don't like that we have to instantiate the same query object - // twice. see if we can streamline this in golgi - let block_query = RelationshipQuery { - source: local_id.clone(), - dest: peer_id.clone(), - }; - - // query block state - context.blocking = match sbot_client.friends_is_blocking(block_query).await { - Ok(blocking) if blocking == "true" => Some(true), - Ok(blocking) if blocking == "false" => Some(false), - _ => None, - }; - - peer_id - } else { - // if an ssb_id has not been provided, retrieve the local id using whoami - context.is_local_profile = true; - - local_id - }; - - // TODO: add relationship state context if not local profile - // ie. lookup is_following and is_blocking, set context accordingly - - // retrieve the profile info for the given id - let info = sbot_client.get_profile_info(&id).await?; - // set each context field accordingly - for (key, val) in info { - match key.as_str() { - "name" => context.name = Some(val), - "description" => context.description = Some(val), - "image" => context.image = Some(val), - _ => (), - } - } - - // assign the ssb public key to the context - // (could be for the local profile or a peer) - context.id = Some(id); - - // determine the path to the blob defined by the value of `context.image` - if let Some(ref blob_id) = context.image { - context.blob_path = match blobs::get_blob_path(&blob_id) { - Ok(path) => { - // if we get the path, check if the blob is in the blobstore. - // this allows us to default to a placeholder image in the template - if let Ok(exists) = utils::blob_is_stored_locally(&path).await { - context.blob_exists = exists - }; - - Some(path) - } - Err(_) => None, - } - } - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} - -#[derive(Debug, Serialize)] -pub struct PrivateContext { - pub back: Option, - pub flash_name: Option, - pub flash_msg: Option, - pub title: Option, - pub theme: Option, - pub sbot_config: Option, - pub sbot_status: Option, - // local peer id (whoami) - pub id: Option, - // id of the peer being messaged - pub recipient_id: Option, -} - -impl PrivateContext { - pub fn default() -> Self { - PrivateContext { - back: Some("/".to_string()), - flash_name: None, - flash_msg: None, - title: Some("Private Messages".to_string()), - theme: None, - sbot_config: None, - sbot_status: None, - id: None, - recipient_id: None, - } - } - - pub async fn build(recipient_id: Option) -> Result { - let mut context = Self::default(); - - // retrieve current ui theme - context.theme = Some(utils::get_theme()); - - // retrieve go-sbot systemd process status - let sbot_status = SbotStatus::read()?; - - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); - - let mut sbot_client = init_sbot_with_config(&sbot_config).await?; - - context.recipient_id = recipient_id; - - let local_id = sbot_client.whoami().await?; - context.id = Some(local_id); - } else { - // the sbot is not currently active; return a helpful message - context.flash_name = Some("warning".to_string()); - context.flash_msg = Some("The Sbot is currently inactive. As a result, private messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); - } - - context.sbot_status = Some(sbot_status); - - Ok(context) - } -} diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 04d537c..bb80031 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -14,8 +14,10 @@ use rocket_dyn_templates::{tera::Context, Template}; use crate::{ context::{ - sbot, - sbot::{BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext}, + scuttlebutt, + scuttlebutt::{ + BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext, + }, }, routes::authentication::Authenticated, utils, @@ -74,7 +76,7 @@ pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash< let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Generating Scuttlebutt invite code"); match sbot_client.invite_create(uses).await { @@ -176,7 +178,7 @@ pub async fn private_post(private: Form, _auth: Authenticated) -> Flash let recipients = vec![id.to_string(), recipient.to_string()]; // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Publishing a new Scuttlebutt private message"); match sbot_client @@ -361,7 +363,7 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Publishing new Scuttlebutt public post"); match sbot_client.publish_post(post_text).await { @@ -412,7 +414,7 @@ pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Following Scuttlebutt peer"); match sbot_client.follow(public_key).await { @@ -463,7 +465,7 @@ pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Unfollowing Scuttlebutt peer"); match sbot_client.unfollow(public_key).await { @@ -514,7 +516,7 @@ pub async fn block(peer: Form, _auth: Authenticated) -> Flash { let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Blocking Scuttlebutt peer"); match sbot_client.block(public_key).await { @@ -565,7 +567,7 @@ pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { debug!("Unblocking Scuttlebutt peer"); match sbot_client.unblock(public_key).await { @@ -705,7 +707,7 @@ pub async fn update_profile_post( let sbot_config = SbotConfig::read().ok(); // initialise sbot connection with ip:port and shscap from config file - match sbot::init_sbot_with_config(&sbot_config).await { + match scuttlebutt::init_sbot_with_config(&sbot_config).await { Ok(mut sbot_client) => { // track whether the name, description or image have been updated let mut name_updated: bool = false; diff --git a/peach-web/src/routes/status/scuttlebutt.rs b/peach-web/src/routes/status/scuttlebutt.rs index 2e9ff06..34aa61d 100644 --- a/peach-web/src/routes/status/scuttlebutt.rs +++ b/peach-web/src/routes/status/scuttlebutt.rs @@ -2,7 +2,7 @@ use rocket::{get, State}; use rocket_dyn_templates::Template; use crate::routes::authentication::Authenticated; -use crate::{context::sbot::StatusContext, RocketConfig}; +use crate::{context::scuttlebutt::StatusContext, RocketConfig}; // HELPERS AND ROUTES FOR /status/scuttlebutt @@ -25,7 +25,7 @@ pub async fn scuttlebutt_status(_auth: Authenticated, config: &State { + Err(_) => { let mut context = StatusContext::default(); // define back arrow url based on mode From 69a8cc262e33e698a65bb94bfbfc923b772706c7 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 3 Mar 2022 08:55:58 +0200 Subject: [PATCH 30/33] renamed context file --- peach-web/src/context/scuttlebutt.rs | 588 +++++++++++++++++++++++++++ 1 file changed, 588 insertions(+) create mode 100644 peach-web/src/context/scuttlebutt.rs diff --git a/peach-web/src/context/scuttlebutt.rs b/peach-web/src/context/scuttlebutt.rs new file mode 100644 index 0000000..9d319a6 --- /dev/null +++ b/peach-web/src/context/scuttlebutt.rs @@ -0,0 +1,588 @@ +use std::collections::HashMap; + +use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot}; +use peach_lib::sbot::{SbotConfig, SbotStatus}; +use rocket::{futures::TryStreamExt, serde::Serialize}; + +use crate::{error::PeachWebError, utils}; + +// HELPER FUNCTIONS + +pub async fn init_sbot_with_config( + sbot_config: &Option, +) -> Result { + // initialise sbot connection with ip:port and shscap from config file + let sbot_client = match sbot_config { + // TODO: panics if we pass `Some(conf.shscap)` as second arg + Some(conf) => { + let ip_port = conf.lis.clone(); + Sbot::init(Some(ip_port), None).await? + } + None => Sbot::init(None, None).await?, + }; + + Ok(sbot_client) +} + +// CONTEXT STRUCTS AND BUILDERS + +#[derive(Debug, Serialize)] +pub struct StatusContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // latest sequence number for the local log + pub latest_seq: Option, +} + +impl StatusContext { + pub fn default() -> Self { + StatusContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Scuttlebutt Status".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + latest_seq: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + // retrieve the local id + let id = sbot_client.whoami().await?; + + let history_stream = sbot_client.create_history_stream(id).await?; + let mut msgs: Vec = history_stream.try_collect().await?; + + // reverse the list of messages so we can easily reference the latest one + msgs.reverse(); + + // assign the sequence number of the latest msg + context.latest_seq = Some(msgs[0].sequence); + + context.sbot_config = sbot_config; + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, status data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who are blocked by the local account +#[derive(Debug, Serialize)] +pub struct BlocksContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl BlocksContext { + pub fn default() -> Self { + BlocksContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Blocks".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let blocks = sbot_client.get_blocks().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !blocks.is_empty() { + for peer in blocks.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // we do not even attempt to find the blob for a blocked peer, + // since it may be vulgar to cause distress to the local peer. + info.insert("blob_exists".to_string(), "false".to_string()); + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who are followed by the local account +#[derive(Debug, Serialize)] +pub struct FollowsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FollowsContext { + pub fn default() -> Self { + FollowsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Follows".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let key = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&key).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), key.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + // push profile info to peer_list vec + peer_info.push(info) + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +// peers who follow and are followed by the local account (friends) +#[derive(Debug, Serialize)] +pub struct FriendsContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + pub peers: Option>>, +} + +impl FriendsContext { + pub fn default() -> Self { + FriendsContext { + back: Some("/scuttlebutt/peers".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Friends".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + peers: None, + } + } + + pub async fn build() -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + let follows = sbot_client.get_follows().await?; + + // we'll use this to store the profile info for each peer who follows us + let mut peer_info = Vec::new(); + + if !follows.is_empty() { + for peer in follows.iter() { + // trim whitespace (including newline characters) and + // remove the inverted-commas around the id + let peer_id = peer.trim().replace('"', ""); + // retrieve the profile info for the given peer + let mut info = sbot_client.get_profile_info(&peer_id).await?; + // insert the public key of the peer into the info hashmap + info.insert("id".to_string(), peer_id.to_string()); + // retrieve the profile image blob id for the given peer + if let Some(blob_id) = info.get("image") { + // look-up the path for the image blob + if let Ok(blob_path) = blobs::get_blob_path(&blob_id) { + // insert the image blob path of the peer into the info hashmap + info.insert("blob_path".to_string(), blob_path.to_string()); + // check if the blob is in the blobstore + // set a flag in the info hashmap + match utils::blob_is_stored_locally(&blob_path).await { + Ok(exists) if exists == true => { + info.insert("blob_exists".to_string(), "true".to_string()) + } + _ => info.insert("blob_exists".to_string(), "false".to_string()), + }; + } + } + + // check if the peer follows us (making us friends) + let follow_query = RelationshipQuery { + source: peer_id.to_string(), + dest: local_id.clone(), + }; + + // query follow state + match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => { + // only push profile info to peer_list vec if they follow us + peer_info.push(info) + } + _ => (), + }; + } + + context.peers = Some(peer_info) + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, peer data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +#[derive(Debug, Serialize)] +pub struct ProfileContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // is this the local profile or the profile of a peer? + pub is_local_profile: bool, + // an ssb_id which may or may not be the local public key + pub id: Option, + pub name: Option, + pub description: Option, + pub image: Option, + // the path to the blob defined in the `image` field (aka the profile picture) + pub blob_path: Option, + // whether or not the blob exists in the blobstore (ie. is saved on disk) + pub blob_exists: bool, + // relationship state (if the profile being viewed is not for the local public key) + pub following: Option, + pub blocking: Option, +} + +impl ProfileContext { + pub fn default() -> Self { + ProfileContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Profile".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + is_local_profile: true, + id: None, + name: None, + description: None, + image: None, + blob_path: None, + blob_exists: false, + following: None, + blocking: None, + } + } + + pub async fn build(ssb_id: Option) -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + let local_id = sbot_client.whoami().await?; + + // if an ssb_id has been provided to the context builder, we assume that + // the profile info being retrieved is for a peer (ie. not for our local + // profile) + let id = if ssb_id.is_some() { + // we are not dealing with the local profile + context.is_local_profile = false; + + // we're safe to unwrap here because we know it's `Some(id)` + let peer_id = ssb_id.unwrap(); + + // determine relationship between peer and local id + let follow_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query follow state + context.following = match sbot_client.friends_is_following(follow_query).await { + Ok(following) if following == "true" => Some(true), + Ok(following) if following == "false" => Some(false), + _ => None, + }; + + // TODO: i don't like that we have to instantiate the same query object + // twice. see if we can streamline this in golgi + let block_query = RelationshipQuery { + source: local_id.clone(), + dest: peer_id.clone(), + }; + + // query block state + context.blocking = match sbot_client.friends_is_blocking(block_query).await { + Ok(blocking) if blocking == "true" => Some(true), + Ok(blocking) if blocking == "false" => Some(false), + _ => None, + }; + + peer_id + } else { + // if an ssb_id has not been provided, retrieve the local id using whoami + context.is_local_profile = true; + + local_id + }; + + // TODO: add relationship state context if not local profile + // ie. lookup is_following and is_blocking, set context accordingly + + // retrieve the profile info for the given id + let info = sbot_client.get_profile_info(&id).await?; + // set each context field accordingly + for (key, val) in info { + match key.as_str() { + "name" => context.name = Some(val), + "description" => context.description = Some(val), + "image" => context.image = Some(val), + _ => (), + } + } + + // assign the ssb public key to the context + // (could be for the local profile or a peer) + context.id = Some(id); + + // determine the path to the blob defined by the value of `context.image` + if let Some(ref blob_id) = context.image { + context.blob_path = match blobs::get_blob_path(&blob_id) { + Ok(path) => { + // if we get the path, check if the blob is in the blobstore. + // this allows us to default to a placeholder image in the template + if let Ok(exists) = utils::blob_is_stored_locally(&path).await { + context.blob_exists = exists + }; + + Some(path) + } + Err(_) => None, + } + } + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, profile data cannot be retrieved. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} + +#[derive(Debug, Serialize)] +pub struct PrivateContext { + pub back: Option, + pub flash_name: Option, + pub flash_msg: Option, + pub title: Option, + pub theme: Option, + pub sbot_config: Option, + pub sbot_status: Option, + // local peer id (whoami) + pub id: Option, + // id of the peer being messaged + pub recipient_id: Option, +} + +impl PrivateContext { + pub fn default() -> Self { + PrivateContext { + back: Some("/".to_string()), + flash_name: None, + flash_msg: None, + title: Some("Private Messages".to_string()), + theme: None, + sbot_config: None, + sbot_status: None, + id: None, + recipient_id: None, + } + } + + pub async fn build(recipient_id: Option) -> Result { + let mut context = Self::default(); + + // retrieve current ui theme + context.theme = Some(utils::get_theme()); + + // retrieve go-sbot systemd process status + let sbot_status = SbotStatus::read()?; + + // we only want to try and interact with the sbot if it's active + if sbot_status.state == Some("active".to_string()) { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); + + let mut sbot_client = init_sbot_with_config(&sbot_config).await?; + + context.recipient_id = recipient_id; + + let local_id = sbot_client.whoami().await?; + context.id = Some(local_id); + } else { + // the sbot is not currently active; return a helpful message + context.flash_name = Some("warning".to_string()); + context.flash_msg = Some("The Sbot is currently inactive. As a result, private messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again".to_string()); + } + + context.sbot_status = Some(sbot_status); + + Ok(context) + } +} From 59ef5960a4daefa7dbe637c6848cce84fc6ccb4e Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 3 Mar 2022 09:33:45 +0200 Subject: [PATCH 31/33] reduce code repetition --- peach-web/src/routes/scuttlebutt.rs | 698 ++++++++++++++-------------- 1 file changed, 339 insertions(+), 359 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index bb80031..01aa108 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -23,6 +23,68 @@ use crate::{ utils, }; +// HELPER FUNCTIONS + +/// Check to see if the go-sbot.service process is currently active. +/// Return an error in the form of a `String` if the process +/// check command fails. Otherwise, return the state of the process. +fn is_sbot_active() -> Result { + // retrieve go-sbot systemd process status + let sbot_status = match SbotStatus::read() { + Ok(status) => status, + Err(e) => return Err(format!("Failed to read sbot status: {}", e)), + }; + + if sbot_status.state == Some("active".to_string()) { + Ok(true) + } else { + Ok(false) + } +} + +/// Ensure that the given public key is a valid ed25519 key. +fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash> { + // ensure the id starts with the correct sigil link + if !public_key.starts_with('@') { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: expected '@' sigil as first character", + )); + } + + // find the dot index denoting the start of the algorithm definition tag + let dot_index = match public_key.rfind('.') { + Some(index) => index, + None => { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: no dot index was found", + )) + } + }; + + // check hashing algorithm (must end with ".ed25519") + if !&public_key.ends_with(".ed25519") { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: hashing algorithm must be ed25519", + )); + } + + // obtain the base64 portion (substring) of the public key + let base64_str = &public_key[1..dot_index]; + + // length of a base64 encoded ed25519 public key + if base64_str.len() != 44 { + return Err(Flash::error( + Redirect::to(redirect_url), + "Invalid key: base64 data length is incorrect", + )); + } + + Ok(()) +} + // HELPERS AND ROUTES FOR INVITES #[get("/invites")] @@ -66,38 +128,39 @@ pub async fn create_invite(invite: Form, _auth: Authenticated) -> Flash< let url = uri!("/scuttlebutt", invites); - // retrieve go-sbot systemd process status - // TODO: handle unwrap properly - let sbot_status = SbotStatus::read().unwrap(); - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Generating Scuttlebutt invite code"); - match sbot_client.invite_create(uses).await { - // construct a custom flash msg to pass along the invite code - Ok(code) => Flash::new(Redirect::to(url), "code", code), - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to create invite code: {}", e), - ), + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Generating Scuttlebutt invite code"); + match sbot_client.invite_create(uses).await { + // construct a custom flash msg to pass along the invite code + Ok(code) => Flash::new(Redirect::to(url), "code", code), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to create invite code: {}", e), + ), + } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ) + } + // failed to retrieve go-sbot systemd process status + Err(e) => return Flash::error(Redirect::to(url), e) } } @@ -154,56 +217,50 @@ pub struct Private { pub async fn private_post(private: Form, _auth: Authenticated) -> Flash { let url = uri!("/scuttlebutt", private(None::)); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - let id = &private.id; - let text = &private.text; - let recipient = &private.recipient; - // now we need to add the local id to the recipients vector, - // otherwise the local id will not be able to read the message. - let recipients = vec![id.to_string(), recipient.to_string()]; + let id = &private.id; + let text = &private.text; + let recipient = &private.recipient; + // now we need to add the local id to the recipients vector, + // otherwise the local id will not be able to read the message. + let recipients = vec![id.to_string(), recipient.to_string()]; - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Publishing a new Scuttlebutt private message"); - match sbot_client - .publish_private(text.to_string(), recipients) - .await - { - Ok(_) => { - Flash::success(Redirect::to(url), format!("Published private message")) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing a new Scuttlebutt private message"); + match sbot_client + .publish_private(text.to_string(), recipients) + .await + { + Ok(_) => { + Flash::success(Redirect::to(url), format!("Published private message")) + } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to publish private message: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to publish private message: {}", e), - ), } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, new private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new private message cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + // failed to retrieve go-sbot systemd process status + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -240,48 +297,6 @@ pub struct Peer { pub public_key: String, } -fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash> { - // ensure the id starts with the correct sigil link - if !public_key.starts_with('@') { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: expected '@' sigil as first character", - )); - } - - // find the dot index denoting the start of the algorithm definition tag - let dot_index = match public_key.rfind('.') { - Some(index) => index, - None => { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: no dot index was found", - )) - } - }; - - // check hashing algorithm (must end with ".ed25519") - if !&public_key.ends_with(".ed25519") { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: hashing algorithm must be ed25519", - )); - } - - // obtain the base64 portion (substring) of the public key - let base64_str = &public_key[1..dot_index]; - - // length of a base64 encoded ed25519 public key - if base64_str.len() != 44 { - return Err(Flash::error( - Redirect::to(redirect_url), - "Invalid key: base64 data length is incorrect", - )); - } - - Ok(()) -} - /// Accept the peer search form and redirect to the profile for that peer. #[post("/search", data = "")] pub async fn search_post(peer: Form, _auth: Authenticated) -> Flash { @@ -291,6 +306,7 @@ pub async fn search_post(peer: Form, _auth: Authenticated) -> Flash, _auth: Authenticated) -> Flash { let post_text = &post.text; - let url = uri!("/scuttlebutt", profile(None::)); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Publishing new Scuttlebutt public post"); - match sbot_client.publish_post(post_text).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Published post")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to publish post: {}", e)) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Publishing new Scuttlebutt public post"); + match sbot_client.publish_post(post_text).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Published post")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to publish post: {}", e), + ), } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, new posts cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -394,46 +403,38 @@ pub async fn publish(post: Form, _auth: Authenticated) -> Flash #[post("/follow", data = "")] pub async fn follow(peer: Form, _auth: Authenticated) -> Flash { let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Following Scuttlebutt peer"); - match sbot_client.follow(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Followed peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to follow peer: {}", e)) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Following Scuttlebutt peer"); + match sbot_client.follow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Followed peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to follow peer: {}", e)) + } } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -448,43 +449,37 @@ pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash let url = uri!("/scuttlebutt", profile(Some(public_key))); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Unfollowing Scuttlebutt peer"); - match sbot_client.unfollow(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Unfollowed peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to unfollow peer: {}", e)) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unfollowing Scuttlebutt peer"); + match sbot_client.unfollow(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unfollowed peer")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to unfollow peer: {}", e), + ), } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -496,46 +491,38 @@ pub async fn unfollow(peer: Form, _auth: Authenticated) -> Flash #[post("/block", data = "")] pub async fn block(peer: Form, _auth: Authenticated) -> Flash { let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Blocking Scuttlebutt peer"); - match sbot_client.block(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Blocked peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to block peer: {}", e)) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Blocking Scuttlebutt peer"); + match sbot_client.block(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Blocked peer")), + Err(e) => { + Flash::error(Redirect::to(url), format!("Failed to block peer: {}", e)) + } } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -547,46 +534,39 @@ pub async fn block(peer: Form, _auth: Authenticated) -> Flash { #[post("/unblock", data = "")] pub async fn unblock(peer: Form, _auth: Authenticated) -> Flash { let public_key = &peer.public_key; - let url = uri!("/scuttlebutt", profile(Some(public_key))); - // retrieve go-sbot systemd process status - let sbot_status = match SbotStatus::read() { - Ok(status) => status, - Err(e) => { - return Flash::error( - Redirect::to(url), - format!("Failed to read sbot status: {}", e), - ) - } - }; - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - debug!("Unblocking Scuttlebutt peer"); - match sbot_client.unblock(public_key).await { - Ok(_) => Flash::success(Redirect::to(url), format!("Unblocked peer")), - Err(e) => { - Flash::error(Redirect::to(url), format!("Failed to unblock peer: {}", e)) + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + debug!("Unblocking Scuttlebutt peer"); + match sbot_client.unblock(public_key).await { + Ok(_) => Flash::success(Redirect::to(url), format!("Unblocked peer")), + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to unblock peer: {}", e), + ), } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, follow messages cannot be published. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } @@ -697,105 +677,105 @@ pub async fn update_profile_post( ) -> Flash { let url = uri!("/scuttlebutt", update_profile); - // retrieve go-sbot systemd process status - // TODO: handle unwrap properly - let sbot_status = SbotStatus::read().unwrap(); - // we only want to try and interact with the sbot if it's active - if sbot_status.state == Some("active".to_string()) { - // retrieve latest go-sbot configuration parameters - let sbot_config = SbotConfig::read().ok(); + match is_sbot_active() { + Ok(true) => { + // retrieve latest go-sbot configuration parameters + let sbot_config = SbotConfig::read().ok(); - // initialise sbot connection with ip:port and shscap from config file - match scuttlebutt::init_sbot_with_config(&sbot_config).await { - Ok(mut sbot_client) => { - // track whether the name, description or image have been updated - let mut name_updated: bool = false; - let mut description_updated: bool = false; - let image_updated: bool; + // initialise sbot connection with ip:port and shscap from config file + match scuttlebutt::init_sbot_with_config(&sbot_config).await { + Ok(mut sbot_client) => { + // track whether the name, description or image have been updated + let mut name_updated: bool = false; + let mut description_updated: bool = false; + let image_updated: bool; - // only update the name if it has changed - if profile.new_name != profile.current_name { - debug!("Publishing new Scuttlebutt profile name"); - let publish_name_res = sbot_client.publish_name(&profile.new_name).await; - if publish_name_res.is_err() { - return Flash::error( - Redirect::to(url), - format!("Failed to update name: {}", publish_name_res.unwrap_err()), - ); - } else { - name_updated = true - } - } - - // only update the description if it has changed - if profile.new_description != profile.current_description { - debug!("Publishing new Scuttlebutt profile description"); - let publish_description_res = sbot_client - .publish_description(&profile.new_description) - .await; - - if publish_description_res.is_err() { - return Flash::error( - Redirect::to(url), - format!( - "Failed to update description: {}", - publish_description_res.unwrap_err() - ), - ); - } else { - description_updated = true - } - } - - // only update the image if a file was uploaded - if profile.image.name().is_some() { - match utils::write_blob_to_store(&mut profile.image).await { - Ok(blob_id) => { - // if the file was successfully added to the blobstore, - // publish an about image message with the blob id - let publish_image_res = sbot_client.publish_image(&blob_id).await; - - if publish_image_res.is_err() { - return Flash::error( - Redirect::to(url), - format!( - "Failed to update image: {}", - publish_image_res.unwrap_err() - ), - ); - } else { - image_updated = true - } - } - Err(e) => { + // only update the name if it has changed + if profile.new_name != profile.current_name { + debug!("Publishing new Scuttlebutt profile name"); + let publish_name_res = sbot_client.publish_name(&profile.new_name).await; + if publish_name_res.is_err() { return Flash::error( Redirect::to(url), - format!("Failed to add image to blobstore: {}", e), - ) + format!("Failed to update name: {}", publish_name_res.unwrap_err()), + ); + } else { + name_updated = true } } - } else { - image_updated = false - } - if name_updated || description_updated || image_updated { - return Flash::success(Redirect::to(url), "Profile updated"); - } else { - // no updates were made but no errors were encountered either - return Flash::success(Redirect::to(url), "Profile info unchanged"); + // only update the description if it has changed + if profile.new_description != profile.current_description { + debug!("Publishing new Scuttlebutt profile description"); + let publish_description_res = sbot_client + .publish_description(&profile.new_description) + .await; + + if publish_description_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update description: {}", + publish_description_res.unwrap_err() + ), + ); + } else { + description_updated = true + } + } + + // only update the image if a file was uploaded + if profile.image.name().is_some() { + match utils::write_blob_to_store(&mut profile.image).await { + Ok(blob_id) => { + // if the file was successfully added to the blobstore, + // publish an about image message with the blob id + let publish_image_res = sbot_client.publish_image(&blob_id).await; + + if publish_image_res.is_err() { + return Flash::error( + Redirect::to(url), + format!( + "Failed to update image: {}", + publish_image_res.unwrap_err() + ), + ); + } else { + image_updated = true + } + } + Err(e) => { + return Flash::error( + Redirect::to(url), + format!("Failed to add image to blobstore: {}", e), + ) + } + } + } else { + image_updated = false + } + + if name_updated || description_updated || image_updated { + return Flash::success(Redirect::to(url), "Profile updated"); + } else { + // no updates were made but no errors were encountered either + return Flash::success(Redirect::to(url), "Profile info unchanged"); + } } + Err(e) => Flash::error( + Redirect::to(url), + format!("Failed to initialise sbot: {}", e), + ), } - Err(e) => Flash::error( - Redirect::to(url), - format!("Failed to initialise sbot: {}", e), - ), } - } else { - return Flash::warning( - Redirect::to(url), - "The Sbot is currently inactive. As a result, profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", - ); + Ok(false) => { + return Flash::warning( + Redirect::to(url), + "The Sbot is currently inactive. As a result, profile data cannot be updated. Visit the Scuttlebutt settings menu to start the Sbot and then try again", + ); + } + Err(e) => return Flash::error(Redirect::to(url), e), } } From 3991af11c716b7b1a5284131d3dfea36fa3eefda Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 3 Mar 2022 09:34:30 +0200 Subject: [PATCH 32/33] only render private if sbot is active, fix type in profile --- .../templates/scuttlebutt/private.html.tera | 23 ++++++++++--------- .../templates/scuttlebutt/profile.html.tera | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/peach-web/templates/scuttlebutt/private.html.tera b/peach-web/templates/scuttlebutt/private.html.tera index 45c6f72..70177b9 100644 --- a/peach-web/templates/scuttlebutt/private.html.tera +++ b/peach-web/templates/scuttlebutt/private.html.tera @@ -2,21 +2,22 @@ {%- block card %}
+ {# only render the private message elements if the sbot is active #} + {%- if sbot_status and sbot_status.state == "active" %}
-
-
- - -
- - - - +
+ +
+ + + + - - {% include "snippets/flash_message" %} + {%- endif %} + + {% include "snippets/flash_message" %}
{%- endblock card -%} diff --git a/peach-web/templates/scuttlebutt/profile.html.tera b/peach-web/templates/scuttlebutt/profile.html.tera index a4cde01..3d0b4b3 100644 --- a/peach-web/templates/scuttlebutt/profile.html.tera +++ b/peach-web/templates/scuttlebutt/profile.html.tera @@ -2,7 +2,7 @@ {%- block card %}
- {# only render the profile info elements is the sbot is active #} + {# only render the profile info elements if the sbot is active #} {%- if sbot_status and sbot_status.state == "active" %}
From 486518002d727b02bbf54ecb21605f090e7158e4 Mon Sep 17 00:00:00 2001 From: glyph Date: Thu, 3 Mar 2022 09:36:48 +0200 Subject: [PATCH 33/33] add latest lockfile --- Cargo.lock | 644 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 537 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af68aa8..449c722 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,16 +89,171 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote 1.0.14", + "syn 1.0.85", +] + +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab 0.4.5", +] + +[[package]] +name = "async-global-executor" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9586ec52317f36de58453159d48351bc244bc24ced3effc1fce22f3d48664af6" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log 0.4.14", + "once_cell", + "parking", + "polling", + "slab 0.4.5", + "socket2", + "waker-fn", + "winapi 0.3.9", +] + +[[package]] +name = "async-lock" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6a8ea61bf9947a1007c5cada31e647dbc77b103c679858150003ba697ea798b" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if 1.0.0", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi 0.3.9", +] + +[[package]] +name = "async-std" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils 0.8.6", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log 0.4.14", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab 0.4.5", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" +dependencies = [ + "async-stream-impl 0.2.1", + "futures-core", +] + [[package]] name = "async-stream" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625" dependencies = [ - "async-stream-impl", + "async-stream-impl 0.3.2", "futures-core", ] +[[package]] +name = "async-stream-impl" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "async-stream-impl" version = "0.3.2" @@ -110,6 +265,12 @@ dependencies = [ "syn 1.0.85", ] +[[package]] +name = "async-task" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d306121baf53310a3fd342d88dc0824f6bbeace68347593658525565abee8" + [[package]] name = "async-trait" version = "0.1.52" @@ -130,6 +291,12 @@ dependencies = [ "autocfg 1.0.1", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "atomicwrites" version = "0.2.5" @@ -195,6 +362,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "base64" version = "0.13.0" @@ -264,6 +437,20 @@ dependencies = [ "byte-tools", ] +[[package]] +name = "blocking" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046e47d4b2d391b1f6f8b407b1deb8dee56c1852ccd868becf2710f601b5f427" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "bstr" version = "0.2.17" @@ -320,6 +507,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + [[package]] name = "cast" version = "0.2.7" @@ -415,6 +608,15 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + [[package]] name = "const_fn" version = "0.4.9" @@ -438,7 +640,7 @@ dependencies = [ "hkdf", "percent-encoding 2.1.0", "rand 0.8.4", - "sha2", + "sha2 0.9.9", "subtle", "time 0.2.27", "version_check 0.9.4", @@ -538,11 +740,12 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d6b536309245c849479fba3da410962a43ed8e51c26b729208ec0ac2798d0" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" dependencies = [ "generic-array 0.14.5", + "typenum", ] [[package]] @@ -555,6 +758,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "ctor" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc0a48a9b826acdf4028595adc9db92caea352f7af011a3034acd172a52a0aa" +dependencies = [ + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "ctr" version = "0.6.0" @@ -646,13 +859,22 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.1" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b697d66081d42af4fba142d56918a3cb21dc8eb63372c6b85d14f44fb9c5979b" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" dependencies = [ "block-buffer 0.10.0", "crypto-common", - "generic-array 0.14.5", +] + +[[package]] +name = "dirs" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" +dependencies = [ + "cfg-if 0.1.10", + "dirs-sys", ] [[package]] @@ -664,6 +886,15 @@ dependencies = [ "dirs-sys", ] +[[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.6" @@ -771,6 +1002,12 @@ dependencies = [ "version_check 0.9.4", ] +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "failure" version = "0.1.8" @@ -840,21 +1077,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "fsevent" version = "0.4.0" @@ -914,9 +1136,9 @@ checksum = "3a471a38ef8ed83cd6e40aa59c1ffe17db6855c18e3604d9c4ed8c08ebc28678" [[package]] name = "futures" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -929,9 +1151,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -939,9 +1161,9 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-cpupool" @@ -955,9 +1177,9 @@ dependencies = [ [[package]] name = "futures-executor" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -967,15 +1189,30 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] [[package]] name = "futures-macro" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", @@ -984,21 +1221,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.19" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures 0.1.31", "futures-channel", @@ -1141,6 +1378,35 @@ dependencies = [ "walkdir", ] +[[package]] +name = "gloo-timers" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d12a7f4e95cfe710f1d624fb1210b7d961a5fb05c4fd942f4feab06e61f590e" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "golgi" +version = "0.1.1" +dependencies = [ + "async-std", + "async-stream 0.3.2", + "base64 0.13.0", + "futures 0.3.21", + "hex", + "kuska-handshake", + "kuska-sodiumoxide", + "kuska-ssb", + "serde 1.0.133", + "serde_json", + "sha2 0.10.2", +] + [[package]] name = "gpio-cdev" version = "0.2.0" @@ -1214,6 +1480,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.10.0" @@ -1501,6 +1773,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "jsonrpc-client-core" version = "0.5.0" @@ -1536,7 +1817,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b99d4207e2a04fb4581746903c2bb7eb376f88de9c699d0f3e10feeac0cd3a" dependencies = [ "derive_more", - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core 18.0.0", "jsonrpc-pubsub 18.0.0", "log 0.4.14", @@ -1577,7 +1858,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14f7f76aef2d054868398427f6c54943cf3d1caa9a7ec7d0c38d69df97a965eb" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "futures-executor", "futures-util", "log 0.4.14", @@ -1606,7 +1887,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b51da17abecbdab3e3d4f26b01c5ec075e88d3abe3ab3b05dc9aa69392764ec0" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-client-transports", ] @@ -1630,7 +1911,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1dea6e07251d9ce6a552abfb5d7ad6bc290a4596c8dcc3d795fae2bbdc1f3ff" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "hyper 0.14.16", "jsonrpc-core 18.0.0", "jsonrpc-server-utils 18.0.0", @@ -1658,7 +1939,7 @@ version = "18.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240f87695e6c6f62fb37f05c02c04953cf68d6408b8c1c89de85c7a0125b1011" dependencies = [ - "futures 0.3.19", + "futures 0.3.21", "jsonrpc-core 18.0.0", "lazy_static", "log 0.4.14", @@ -1691,7 +1972,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4fdea130485b572c39a460d50888beb00afb3e35de23ccd7fad8ff19f0e0d4" dependencies = [ "bytes 1.1.0", - "futures 0.3.19", + "futures 0.3.21", "globset", "jsonrpc-core 18.0.0", "lazy_static", @@ -1760,6 +2041,60 @@ dependencies = [ "winapi-build", ] +[[package]] +name = "kuska-handshake" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33da4b69f23c2ece0b3e729d079cebdc2c0206e493e42f510f500ad81c631d5" +dependencies = [ + "futures 0.3.21", + "hex", + "kuska-sodiumoxide", + "log 0.4.14", + "thiserror", +] + +[[package]] +name = "kuska-sodiumoxide" +version = "0.2.5-0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0f8eafdd240b722243787b51fdaf8df6693fb8621d0f7061cdba574214cf88" +dependencies = [ + "libc", + "libsodium-sys", + "serde 1.0.133", +] + +[[package]] +name = "kuska-ssb" +version = "0.4.0" +dependencies = [ + "async-std", + "async-stream 0.2.1", + "base64 0.11.0", + "dirs 2.0.2", + "futures 0.3.21", + "get_if_addrs", + "hex", + "kuska-handshake", + "kuska-sodiumoxide", + "log 0.4.14", + "once_cell", + "regex", + "serde 1.0.133", + "serde_json", + "thiserror", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log 0.4.14", +] + [[package]] name = "language-tags" version = "0.2.2" @@ -1784,6 +2119,18 @@ version = "0.2.112" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" +[[package]] +name = "libsodium-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b779387cd56adfbc02ea4a668e704f729be8d6a6abd2c27ca5ee537849a92fd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "walkdir", +] + [[package]] name = "linked-hash-map" version = "0.3.0" @@ -1857,6 +2204,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", + "value-bag", ] [[package]] @@ -2313,43 +2661,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" -dependencies = [ - "bitflags 1.3.2", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-src" -version = "111.17.0+1.1.1m" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d6a336abd10814198f66e2a91ccd7336611f30334119ca8ce300536666fcf4" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" -dependencies = [ - "autocfg 1.0.1", - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - [[package]] name = "owning_ref" version = "0.4.1" @@ -2359,6 +2670,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.7.1" @@ -2501,6 +2818,7 @@ name = "peach-lib" version = "1.3.2" dependencies = [ "chrono", + "dirs 4.0.0", "fslock", "jsonrpc-client-core", "jsonrpc-client-http", @@ -2512,6 +2830,7 @@ dependencies = [ "serde_json", "serde_yaml", "sha3", + "toml", ] [[package]] @@ -2574,7 +2893,7 @@ dependencies = [ [[package]] name = "peach-stats" -version = "0.2.0" +version = "0.3.0" dependencies = [ "log 0.4.14", "miniserde", @@ -2587,20 +2906,21 @@ dependencies = [ name = "peach-web" version = "0.5.0" dependencies = [ + "base64 0.13.0", + "dirs 4.0.0", "env_logger 0.8.4", + "golgi", + "lazy_static", "log 0.4.14", "nest", - "openssl", "peach-lib", "peach-network", "peach-stats", - "percent-encoding 2.1.0", - "regex", "rocket", "rocket_dyn_templates", "serde 1.0.133", "serde_json", - "snafu 0.6.10", + "temporary", "tera", "xdg", ] @@ -2740,6 +3060,19 @@ version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "log 0.4.14", + "wepoll-ffi", + "winapi 0.3.9", +] + [[package]] name = "polyval" version = "0.4.5" @@ -2764,7 +3097,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f77e66f6d6d898cbbd4a09c48fd3507cfc210b7c83055de02a38b5f7a1e6d216" dependencies = [ "libc", - "time 0.1.44", + "time 0.2.27", ] [[package]] @@ -3072,6 +3405,12 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "random" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d13a3485349981c90c79112a11222c3e6e75de1d52b87a7525b3bf5361420f" + [[package]] name = "rdrand" version = "0.4.0" @@ -3176,7 +3515,7 @@ version = "0.5.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a71c18c42a0eb15bf3816831caf0dad11e7966f2a41aaf486a701979c4dd1f2" dependencies = [ - "async-stream", + "async-stream 0.3.2", "async-trait", "atomic", "atty", @@ -3184,7 +3523,7 @@ dependencies = [ "bytes 1.1.0", "either", "figment", - "futures 0.3.19", + "futures 0.3.21", "indexmap", "log 0.4.14", "memchr", @@ -3492,13 +3831,24 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sha2" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "digest 0.10.3", +] + [[package]] name = "sha3" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ - "digest 0.10.1", + "digest 0.10.3", "keccak", ] @@ -3511,6 +3861,16 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -3883,6 +4243,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "temporary" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acd7ed303e9bfb202712600e621300c9dfacfdc4be55735e61bd2f26932892d2" +dependencies = [ + "random", +] + [[package]] name = "tera" version = "1.15.0" @@ -3923,6 +4292,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2 1.0.36", + "quote 1.0.14", + "syn 1.0.85", +] + [[package]] name = "thread_local" version = "1.1.3" @@ -4547,10 +4936,14 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "value-bag" +version = "1.0.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check 0.9.4", +] [[package]] name = "vec_map" @@ -4576,6 +4969,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "walkdir" version = "2.3.2" @@ -4633,9 +5032,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" [[package]] name = "wasm-bindgen" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" +checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", @@ -4643,9 +5042,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" +checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" dependencies = [ "bumpalo", "lazy_static", @@ -4657,10 +5056,22 @@ dependencies = [ ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" +name = "wasm-bindgen-futures" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" +checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" dependencies = [ "quote 1.0.14", "wasm-bindgen-macro-support", @@ -4668,9 +5079,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" +checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" dependencies = [ "proc-macro2 1.0.36", "quote 1.0.14", @@ -4681,9 +5092,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.78" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" +checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" + +[[package]] +name = "web-sys" +version = "0.3.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] [[package]] name = "winapi" @@ -4771,7 +5201,7 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a23fe958c70412687039c86f578938b4a0bb50ec788e96bce4d6ab00ddd5803" dependencies = [ - "dirs", + "dirs 3.0.2", ] [[package]]