Merge pull request 'Add SSB routes for newly added templates' (#27) from ssb_routes into main

Reviewed-on: #27
This commit is contained in:
glyph 2021-11-15 10:37:58 +00:00
commit 7f09cc5888
5 changed files with 381 additions and 32 deletions

View File

@ -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<dyn std::error::Error>;
/// Create rocket instance & mount all routes.
fn init_rocket() -> Rocket<Build> {
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<Build> {
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<Build> {
/// 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);
}

View File

@ -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<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
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<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = MessageContext::build();
/// A private message composition and publication page.
#[get("/private")]
pub fn private(flash: Option<FlashMessage>, _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<FlashMessage>, _auth: Authenticated) -> Template {
let mut context = PeerContext::build();
@ -76,6 +84,94 @@ pub fn peers(flash: Option<FlashMessage>, _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 = "<post>")]
pub fn publish(
post: Form<Post>,
flash: Option<FlashMessage>,
_auth: Authenticated,
) -> Flash<Redirect> {
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_key>")]
pub fn follow(
pub_key: Form<PublicKey>,
flash: Option<FlashMessage>,
_auth: Authenticated,
) -> Flash<Redirect> {
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_key>")]
pub fn unfollow(
pub_key: Form<PublicKey>,
flash: Option<FlashMessage>,
_auth: Authenticated,
) -> Flash<Redirect> {
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_key>")]
pub fn block(
pub_key: Form<PublicKey>,
flash: Option<FlashMessage>,
_auth: Authenticated,
) -> Flash<Redirect> {
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<FlashMessage>, _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_key>")]
pub fn profile(
pub_key: Option<&str>,
flash: Option<FlashMessage>,
_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<FlashMessage>, _auth: Authenticated) -> Template {
};
Template::render("profile", &context)
}
// HELPERS AND ROUTES FOR /friends
#[derive(Debug, Serialize)]
pub struct FriendsContext {
pub back: Option<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
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<FlashMessage>, _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<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
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<FlashMessage>, _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<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
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<FlashMessage>, _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<String>,
pub flash_name: Option<String>,
pub flash_msg: Option<String>,
pub title: Option<String>,
}
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<FlashMessage>, _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)
}

View File

@ -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]

View File

@ -4,21 +4,21 @@
<div class="grid">
<!-- top-left -->
<!-- PEERS LINK AND ICON -->
<a class="top-left" href="/peers" title="Scuttlebutt Peers">
<a class="top-left" href="/scuttlebutt/peers" title="Scuttlebutt Peers">
<div class="circle circle-small">
<img class="icon-medium" src="icons/users.svg">
</div>
</a>
<!-- top-middle -->
<!-- CURRENT USER LINK AND ICON -->
<a class="top-middle" href="/profile" title="Profile">
<a class="top-middle" href="/scuttlebutt/profile" title="Profile">
<div class="circle circle-small">
<img class="icon-medium" src="icons/user.svg">
</div>
</a>
<!-- top-right -->
<!-- MESSAGES LINK AND ICON -->
<a class="top-right" href="/messages" title="Private Messages">
<a class="top-right" href="/scuttlebutt/private" title="Private Messages">
<div class="circle circle-small">
<img class="icon-medium" src="icons/envelope.svg">
</div>

View File

@ -21,6 +21,7 @@
<input id="publishPost" class="button button-primary center" title="Publish" type="submit" value="Publish">
</form>
<!-- BUTTONS -->
<!-- TODO: each of these buttons needs to be a form with a public key -->
<div id="buttons">
<a id="followPeer" class="button button-primary center" href="/scuttlebutt/follow" title="Follow Peer">Follow</a>
<a id="blockPeer" class="button button-warning center" href="/scuttlebutt/block" title="Block Peer">Block</a>