From 9756a80094971db6e0792a1133700e45b5c262f9 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 11:59:14 +0200 Subject: [PATCH 1/5] add scuttlebutt routes --- peach-web/src/routes/scuttlebutt.rs | 279 ++++++++++++++++++++++++++-- 1 file changed, 266 insertions(+), 13 deletions(-) diff --git a/peach-web/src/routes/scuttlebutt.rs b/peach-web/src/routes/scuttlebutt.rs index 7e9200d..ec8d826 100644 --- a/peach-web/src/routes/scuttlebutt.rs +++ b/peach-web/src/routes/scuttlebutt.rs @@ -1,24 +1,30 @@ -//! Routes for ScuttleButt related functionality. +//! Routes for Scuttlebutt related functionality. -use rocket::{get, request::FlashMessage}; +use rocket::{ + form::{Form, FromForm}, + get, post, + request::FlashMessage, + response::{Flash, Redirect}, + serde::{Deserialize, Serialize}, + uri, +}; use rocket_dyn_templates::Template; -use serde::Serialize; use crate::routes::authentication::Authenticated; -// HELPERS AND ROUTES FOR /messages +// HELPERS AND ROUTES FOR /private #[derive(Debug, Serialize)] -pub struct MessageContext { +pub struct PrivateContext { pub back: Option, pub flash_name: Option, pub flash_msg: Option, pub title: Option, } -impl MessageContext { - pub fn build() -> MessageContext { - MessageContext { +impl PrivateContext { + pub fn build() -> PrivateContext { + PrivateContext { back: None, flash_name: None, flash_msg: None, @@ -27,9 +33,10 @@ impl MessageContext { } } -#[get("/messages")] -pub fn messages(flash: Option, _auth: Authenticated) -> Template { - let mut context = MessageContext::build(); +/// A private message composition and publication page. +#[get("/private")] +pub fn private(flash: Option, _auth: Authenticated) -> Template { + let mut context = PrivateContext::build(); context.back = Some("/".to_string()); context.title = Some("Private Messages".to_string()); // check to see if there is a flash message to display @@ -62,6 +69,7 @@ impl PeerContext { } } +/// A peer menu which allows navigating to lists of friends, follows, followers and blocks. #[get("/peers")] pub fn peers(flash: Option, _auth: Authenticated) -> Template { let mut context = PeerContext::build(); @@ -76,6 +84,94 @@ pub fn peers(flash: Option, _auth: Authenticated) -> Template { Template::render("peers", &context) } +// HELPERS AND ROUTES FOR /post/publish + +#[derive(Debug, Deserialize, FromForm)] +pub struct Post { + pub text: String, +} + +/// Publish a public Scuttlebutt post. 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("/publish", data = "")] +pub fn publish( + post: Form, + flash: Option, + _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") +} + +// HELPERS AND ROUTES FOR /follow + +#[derive(Debug, Deserialize, 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, + flash: Option, + _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!(profile(Some(public_key))); + let success_msg = format!("Followed {}", public_key); + Flash::success(Redirect::to(profile_url), success_msg) +} + +// HELPERS AND ROUTES FOR /unfollow + +/// 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, + flash: Option, + _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!(profile(Some(public_key))); + let success_msg = format!("Unfollowed {}", public_key); + Flash::success(Redirect::to(profile_url), success_msg) +} + +// HELPERS AND ROUTES FOR /block + +/// Block 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("/block", data = "")] +pub fn block( + pub_key: Form, + flash: Option, + _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!(profile(Some(public_key))); + let success_msg = format!("Blocked {}", public_key); + Flash::success(Redirect::to(profile_url), success_msg) +} + // HELPERS AND ROUTES FOR /profile #[derive(Debug, Serialize)] @@ -97,8 +193,13 @@ impl ProfileContext { } } -#[get("/profile")] -pub fn profile(flash: Option, _auth: Authenticated) -> Template { +/// 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 { let mut context = ProfileContext::build(); context.back = Some("/".to_string()); context.title = Some("Profile".to_string()); @@ -110,3 +211,155 @@ pub fn profile(flash: Option, _auth: Authenticated) -> Template { }; Template::render("profile", &context) } + +// 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, +} + +impl FriendsContext { + pub fn build() -> FriendsContext { + FriendsContext { + back: None, + flash_name: None, + flash_msg: None, + title: 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 { + let mut context = FriendsContext::build(); + context.back = Some("/scuttlebutt/peers".to_string()); + context.title = Some("Friends".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("peers_list", &context) +} + +// 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, +} + +impl FollowsContext { + pub fn build() -> FollowsContext { + FollowsContext { + back: None, + flash_name: None, + flash_msg: None, + title: 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 { + let mut context = FollowsContext::build(); + context.back = Some("/scuttlebutt/peers".to_string()); + context.title = Some("Follows".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("peers_list", &context) +} + +// 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, +} + +impl FollowersContext { + pub fn build() -> FollowersContext { + FollowersContext { + back: None, + flash_name: None, + flash_msg: None, + title: 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 { + let mut context = FollowersContext::build(); + context.back = Some("/scuttlebutt/peers".to_string()); + context.title = Some("Followers".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("peers_list", &context) +} + +// 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, +} + +impl BlocksContext { + pub fn build() -> BlocksContext { + BlocksContext { + back: None, + flash_name: None, + flash_msg: None, + title: None, + } + } +} + +/// 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 { + let mut context = BlocksContext::build(); + context.back = Some("/scuttlebutt/peers".to_string()); + context.title = Some("Blocks".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("peers_list", &context) +} -- 2.49.0 From f225cf0dc842be417addcc632d6d0a21596938ce Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 11:59:25 +0200 Subject: [PATCH 2/5] mount scuttlebutt routes --- peach-web/src/main.rs | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/peach-web/src/main.rs b/peach-web/src/main.rs index 1d073e6..ab1941f 100644 --- a/peach-web/src/main.rs +++ b/peach-web/src/main.rs @@ -30,15 +30,15 @@ pub mod routes; mod tests; pub mod utils; -use log::{info, error}; +use log::{error, info}; use std::process; -use rocket::{catchers, routes, Rocket, Build, fs::FileServer}; +use rocket::{catchers, fs::FileServer, routes, Build, Rocket}; use rocket_dyn_templates::Template; use crate::routes::authentication::*; -use crate::routes::device::*; use crate::routes::catchers::*; +use crate::routes::device::*; use crate::routes::index::*; use crate::routes::ping::*; use crate::routes::scuttlebutt::*; @@ -47,12 +47,27 @@ use crate::routes::settings::admin::*; use crate::routes::settings::dns::*; use crate::routes::settings::network::*; - pub type BoxError = Box; /// Create rocket instance & mount all routes. fn init_rocket() -> Rocket { rocket::build() + .mount( + "/scuttlebutt", + routes![ + peers, // WEB ROUTE + friends, // WEB ROUTE + follows, // WEB ROUTE + followers, // WEB ROUTE + blocks, // WEB ROUTE + profile, // WEB ROUTE + private, // WEB ROUTE + follow, // WEB ROUTE + unfollow, // WEB ROUTE + block, // WEB ROUTE + publish, // WEB ROUTE + ], + ) .mount( "/", routes![ @@ -68,13 +83,10 @@ fn init_rocket() -> Rocket { login, // WEB ROUTE login_post, // WEB ROUTE logout, // WEB ROUTE - messages, // WEB ROUTE network_home, // WEB ROUTE network_add_ssid, // WEB ROUTE network_add_wifi, // WEB ROUTE network_detail, // WEB ROUTE - peers, // WEB ROUTE - profile, // WEB ROUTE reboot_cmd, // WEB ROUTE shutdown_cmd, // WEB ROUTE shutdown_menu, // WEB ROUTE @@ -130,7 +142,6 @@ fn init_rocket() -> Rocket { /// Launch the peach-web rocket server. #[rocket::main] async fn main() { - // initialize logger env_logger::init(); @@ -140,7 +151,7 @@ async fn main() { // launch rocket info!("Launching Rocket"); - if let Err(e) = rocket.launch().await { + if let Err(e) = rocket.launch().await { error!("Error in Rocket application: {}", e); process::exit(1); } -- 2.49.0 From 7f4b1b2b0057f997ed1a0e5d92da6280637a68e1 Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 11:59:38 +0200 Subject: [PATCH 3/5] test scuttlebutt routes --- peach-web/src/tests.rs | 98 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 7 deletions(-) diff --git a/peach-web/src/tests.rs b/peach-web/src/tests.rs index 0339192..931af3e 100644 --- a/peach-web/src/tests.rs +++ b/peach-web/src/tests.rs @@ -1,9 +1,9 @@ use std::fs::File; use std::io::Read; -use rocket::serde::json::{Value, json}; use rocket::http::{ContentType, Status}; use rocket::local::blocking::Client; +use rocket::serde::json::{json, Value}; use crate::utils::build_json_response; @@ -46,7 +46,7 @@ fn index_html() { let body = response.into_string().unwrap(); assert!(body.contains("/peers")); assert!(body.contains("/profile")); - assert!(body.contains("/messages")); + assert!(body.contains("/private")); assert!(body.contains("/device")); assert!(body.contains("/help")); assert!(body.contains("/network")); @@ -154,9 +154,9 @@ fn login_html() { } #[test] -fn messages_html() { +fn private_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/messages").dispatch(); + let response = client.get("/scuttlebutt/private").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -166,17 +166,102 @@ fn messages_html() { #[test] fn peers_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/peers").dispatch(); + let response = client.get("/scuttlebutt/peers").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); assert!(body.contains("Scuttlebutt Peers")); } +#[test] +fn friends_html() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client.get("/scuttlebutt/friends").dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::HTML)); + let body = response.into_string().unwrap(); + assert!(body.contains("Friends")); +} + +#[test] +fn follows_html() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client.get("/scuttlebutt/follows").dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::HTML)); + let body = response.into_string().unwrap(); + assert!(body.contains("Follows")); +} + +#[test] +fn followers_html() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client.get("/scuttlebutt/followers").dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::HTML)); + let body = response.into_string().unwrap(); + assert!(body.contains("Followers")); +} + +#[test] +fn block_html() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client.get("/scuttlebutt/blocks").dispatch(); + assert_eq!(response.status(), Status::Ok); + assert_eq!(response.content_type(), Some(ContentType::HTML)); + let body = response.into_string().unwrap(); + assert!(body.contains("Blocks")); +} + +#[test] +fn follow() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client + .post("/scuttlebutt/follow") + .header(ContentType::Form) + .body("key=@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") + .dispatch(); + // ensure we redirect (303) + assert_eq!(response.status(), Status::SeeOther); +} + +#[test] +fn unfollow() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client + .post("/scuttlebutt/unfollow") + .header(ContentType::Form) + .body("key=@HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") + .dispatch(); + assert_eq!(response.status(), Status::SeeOther); +} + +#[test] +fn block() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client + .post("/scuttlebutt/block") + .header(ContentType::Form) + .body("key=HEqy940T6uB+T+d9Jaa58aNfRzLx9eRWqkZljBmnkmk=.ed25519") + .dispatch(); + assert_eq!(response.status(), Status::SeeOther); +} + +#[test] +fn publish_post() { + let client = Client::tracked(init_rocket()).expect("valid rocket instance"); + let response = client + .post("/scuttlebutt/publish") + .header(ContentType::Form) + .body("text='golden ripples in the meshwork'") + .dispatch(); + assert_eq!(response.status(), Status::SeeOther); +} + #[test] fn profile_html() { let client = Client::tracked(init_rocket()).expect("valid rocket instance"); - let response = client.get("/profile").dispatch(); + let response = client.get("/scuttlebutt/profile").dispatch(); assert_eq!(response.status(), Status::Ok); assert_eq!(response.content_type(), Some(ContentType::HTML)); let body = response.into_string().unwrap(); @@ -432,7 +517,6 @@ fn test_build_json_response() { assert_eq!(j["msg"], json!(null)); } - // FILE TESTS #[test] -- 2.49.0 From 0260a4a5dec47441d0f9770d45ca3a6659bba06c Mon Sep 17 00:00:00 2001 From: glyph Date: Mon, 15 Nov 2021 11:59:55 +0200 Subject: [PATCH 4/5] add TODO concerning forms --- peach-web/templates/index.html.tera | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/peach-web/templates/index.html.tera b/peach-web/templates/index.html.tera index 2532a98..b7142ae 100644 --- a/peach-web/templates/index.html.tera +++ b/peach-web/templates/index.html.tera @@ -4,21 +4,21 @@