900 lines
33 KiB
Rust
900 lines
33 KiB
Rust
//! Routes for Scuttlebutt related functionality.
|
|
|
|
use log::debug;
|
|
use peach_lib::sbot::{SbotConfig, SbotStatus};
|
|
use rocket::{
|
|
form::{Form, FromForm},
|
|
fs::TempFile,
|
|
get, post,
|
|
request::FlashMessage,
|
|
response::{Flash, Redirect},
|
|
uri,
|
|
};
|
|
use rocket_dyn_templates::{tera::Context, Template};
|
|
|
|
use crate::{
|
|
context::{
|
|
scuttlebutt,
|
|
scuttlebutt::{
|
|
BlocksContext, FollowsContext, FriendsContext, PrivateContext, ProfileContext,
|
|
},
|
|
},
|
|
routes::authentication::Authenticated,
|
|
utils,
|
|
};
|
|
|
|
// HELPERS AND ROUTES FOR INVITES
|
|
|
|
#[get("/invites")]
|
|
pub fn invites(flash: Option<FlashMessage>, _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, FromForm)]
|
|
pub struct Invite {
|
|
pub uses: u16,
|
|
}
|
|
|
|
#[post("/invites", data = "<invite>")]
|
|
pub async fn create_invite(invite: Form<Invite>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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 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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// A private message composition and publication page.
|
|
#[get("/private?<public_key>")]
|
|
pub async fn private(
|
|
mut public_key: Option<String>,
|
|
flash: Option<FlashMessage<'_>>,
|
|
_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(public_key).await;
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, FromForm)]
|
|
pub struct Private {
|
|
pub id: String,
|
|
pub text: String,
|
|
pub recipient: String,
|
|
}
|
|
|
|
/// Publish a private message.
|
|
#[post("/private", data = "<private>")]
|
|
pub async fn private_post(private: Form<Private>, _auth: Authenticated) -> Flash<Redirect> {
|
|
let url = uri!("/scuttlebutt", private(None::<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),
|
|
)
|
|
}
|
|
};
|
|
|
|
// 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 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 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
|
|
|
|
/// Search for a peer.
|
|
#[get("/search")]
|
|
pub fn search(flash: Option<FlashMessage>, _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("Search"));
|
|
context.insert("back", &Some("/scuttlebutt/peers"));
|
|
|
|
// 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/search", &context.into_json())
|
|
}
|
|
|
|
#[derive(Debug, FromForm)]
|
|
pub struct Peer {
|
|
// public key
|
|
pub public_key: String,
|
|
}
|
|
|
|
fn validate_public_key(public_key: &str, redirect_url: String) -> Result<(), Flash<Redirect>> {
|
|
// 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 = "<peer>")]
|
|
pub async fn search_post(peer: Form<Peer>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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
|
|
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
|
|
|
|
/// 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<FlashMessage>, _auth: Authenticated) -> Template {
|
|
// retrieve current ui theme
|
|
let theme = utils::get_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.insert("flash_name", &Some(flash.kind().to_string()));
|
|
context.insert("flash_msg", &Some(flash.message().to_string()));
|
|
};
|
|
|
|
Template::render("scuttlebutt/peers", &context.into_json())
|
|
}
|
|
|
|
// HELPERS AND ROUTES FOR /post/publish
|
|
|
|
#[derive(Debug, 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 async fn publish(post: Form<Post>, _auth: Authenticated) -> Flash<Redirect> {
|
|
let post_text = &post.text;
|
|
|
|
let url = uri!("/scuttlebutt", profile(None::<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),
|
|
)
|
|
}
|
|
};
|
|
|
|
// 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 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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// 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 = "<peer>")]
|
|
pub async fn follow(peer: Form<Peer>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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 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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// 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 = "<peer>")]
|
|
pub async fn unfollow(peer: Form<Peer>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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 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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// 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 = "<peer>")]
|
|
pub async fn block(peer: Form<Peer>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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 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),
|
|
),
|
|
}
|
|
} 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 = "<peer>")]
|
|
pub async fn unblock(peer: Form<Peer>, _auth: Authenticated) -> Flash<Redirect> {
|
|
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 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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// 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).
|
|
#[get("/profile?<public_key>")]
|
|
pub async fn profile(
|
|
mut public_key: Option<String>,
|
|
flash: Option<FlashMessage<'_>>,
|
|
_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(public_key).await;
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HELPERS AND ROUTES FOR /profile/update
|
|
|
|
/// 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<FlashMessage<'_>>, _auth: Authenticated) -> Template {
|
|
// build the profile context object
|
|
let context = ProfileContext::build(None).await;
|
|
|
|
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
|
|
context.flash_name = Some(flash.kind().to_string());
|
|
context.flash_msg = Some(flash.message().to_string());
|
|
};
|
|
|
|
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, 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, 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 = "<profile>")]
|
|
pub async fn update_profile_post(
|
|
mut profile: Form<Profile<'_>>,
|
|
_auth: Authenticated,
|
|
) -> Flash<Redirect> {
|
|
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();
|
|
|
|
// 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) => {
|
|
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),
|
|
),
|
|
}
|
|
} 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
|
|
|
|
/// A list of friends (mutual follows), with each list item displaying the
|
|
/// name, image and public key of the peer.
|
|
#[get("/friends")]
|
|
pub async fn friends(flash: Option<FlashMessage<'_>>, _auth: Authenticated) -> Template {
|
|
// build the friends context object
|
|
let context = FriendsContext::build().await;
|
|
|
|
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 = FriendsContext::default();
|
|
context.flash_name = Some("error".to_string());
|
|
context.flash_msg = Some(e.to_string());
|
|
|
|
Template::render("scuttlebutt/peers_list", &context)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HELPERS AND ROUTES FOR /follows
|
|
|
|
/// 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 async fn follows(flash: Option<FlashMessage<'_>>, _auth: Authenticated) -> Template {
|
|
// build the follows context object
|
|
let context = FollowsContext::build().await;
|
|
|
|
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 = FollowsContext::default();
|
|
context.flash_name = Some("error".to_string());
|
|
context.flash_msg = Some(e.to_string());
|
|
|
|
Template::render("scuttlebutt/peers_list", &context)
|
|
}
|
|
}
|
|
}
|
|
|
|
// HELPERS AND ROUTES FOR /blocks
|
|
|
|
/// 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<FlashMessage<'_>>, _auth: Authenticated) -> Template {
|
|
// build the blocks context object
|
|
let context = BlocksContext::build().await;
|
|
|
|
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)
|
|
}
|
|
}
|
|
}
|