add profile update template and route handler
This commit is contained in:
parent
3e918f66cf
commit
084af1b486
|
@ -124,6 +124,14 @@ pub fn mount_peachpub_routes(request: &Request) -> Response {
|
|||
Response::html(routes::scuttlebutt::profile::build_template(request, None))
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/profile/update) => {
|
||||
Response::html(routes::scuttlebutt::profile_update::build_template(request))
|
||||
},
|
||||
|
||||
(POST) (/scuttlebutt/profile/update) => {
|
||||
routes::scuttlebutt::profile_update::handle_form(request)
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/profile/{ssb_id: String}) => {
|
||||
debug!("ssb_id: {}", ssb_id);
|
||||
Response::html(routes::scuttlebutt::profile::build_template(request, Some(ssb_id)))
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
use maud::{html, PreEscaped};
|
||||
use peach_lib::sbot::SbotStatus;
|
||||
use rouille::{input::post::BufferedFile, post_input, try_or_400, Request, Response};
|
||||
|
||||
use crate::{
|
||||
templates,
|
||||
utils::{
|
||||
flash::{FlashRequest, FlashResponse},
|
||||
sbot,
|
||||
sbot::Profile,
|
||||
},
|
||||
};
|
||||
|
||||
// ROUTE: /scuttlebutt/profile/update
|
||||
|
||||
fn parse_profile_info(profile: Profile) -> (String, String, String) {
|
||||
let id = match profile.id {
|
||||
Some(id) => id,
|
||||
_ => "Public key unavailable".to_string(),
|
||||
};
|
||||
|
||||
let name = match profile.name {
|
||||
Some(name) => name,
|
||||
_ => "Name unavailable".to_string(),
|
||||
};
|
||||
|
||||
let description = match profile.description {
|
||||
Some(description) => description,
|
||||
_ => "Description unavailable".to_string(),
|
||||
};
|
||||
|
||||
(id, name, description)
|
||||
}
|
||||
|
||||
/// Scuttlebutt profile update template builder.
|
||||
///
|
||||
/// Serve a form for the purpose of updating the name, description and picture
|
||||
/// for the local Scuttlebutt profile.
|
||||
pub fn build_template(request: &Request) -> PreEscaped<String> {
|
||||
// check for flash cookies; will be (None, None) if no flash cookies are found
|
||||
let (flash_name, flash_msg) = request.retrieve_flash();
|
||||
|
||||
let profile_update_template = match SbotStatus::read() {
|
||||
Ok(status) if status.state == Some("active".to_string()) => {
|
||||
// retrieve the local profile info
|
||||
match sbot::get_profile_info(None) {
|
||||
Ok(profile) => {
|
||||
let (id, name, description) = parse_profile_info(profile);
|
||||
|
||||
// render the scuttlebutt profile update form
|
||||
html! {
|
||||
(PreEscaped("<!-- SSB PROFILE UPDATE FORM -->"))
|
||||
div class="card card-wide center" {
|
||||
form id="profileInfo" class="center" enctype="multipart/form-data" action="/scuttlebutt/profile/update" method="post" {
|
||||
div style="display: flex; flex-direction: column" {
|
||||
label for="name" class="label-small font-gray" {
|
||||
"NAME"
|
||||
}
|
||||
input style="margin-bottom: 1rem;" type="text" id="name" name="new_name" placeholder="Choose a name for your profile..." value=(name);
|
||||
label for="description" class="label-small font-gray" {
|
||||
"DESCRIPTION"
|
||||
}
|
||||
textarea id="description" class="message-input" style="margin-bottom: 1rem;" name="new_description" placeholder="Write a description for your profile..." {
|
||||
(description)
|
||||
}
|
||||
label for="image" class="label-small font-gray" {
|
||||
"IMAGE"
|
||||
}
|
||||
input type="file" id="fileInput" class="font-normal" name="image";
|
||||
}
|
||||
input type="hidden" name="id" value=(id);
|
||||
input type="hidden" name="current_name" value=(name);
|
||||
input type="hidden" name="current_description" value=(description);
|
||||
div id="buttonDiv" style="margin-top: 2rem;" {
|
||||
input id="updateProfile" class="button button-primary center" title="Publish" type="submit" value="Publish";
|
||||
a class="button button-secondary center" href="/scuttlebutt/profile" title="Cancel" { "Cancel" }
|
||||
|
||||
}
|
||||
}
|
||||
// render flash message if cookies were found in the request
|
||||
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
|
||||
(PreEscaped("<!-- FLASH MESSAGE -->"))
|
||||
(templates::flash::build_template(name, msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
// render the sbot error template with the error message
|
||||
templates::error::build_template(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// the sbot is not active; render a message instead of the form
|
||||
templates::inactive::build_template("Profile is unavailable.")
|
||||
}
|
||||
};
|
||||
|
||||
let body = templates::nav::build_template(
|
||||
profile_update_template,
|
||||
"Profile",
|
||||
Some("/scuttlebutt/profile"),
|
||||
);
|
||||
|
||||
templates::base::build_template(body)
|
||||
}
|
||||
|
||||
/// 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).
|
||||
pub fn handle_form(request: &Request) -> Response {
|
||||
// query the request body for form data
|
||||
// return a 400 error if the admin_id field is missing
|
||||
let data = try_or_400!(post_input!(request, {
|
||||
id: String,
|
||||
current_name: String,
|
||||
current_description: String,
|
||||
new_name: Option<String>,
|
||||
new_description: Option<String>,
|
||||
image: Option<BufferedFile>,
|
||||
}));
|
||||
|
||||
let (flash_name, flash_msg) = match SbotStatus::read() {
|
||||
Ok(status) if status.state == Some("active".to_string()) => {
|
||||
// we can't pass `data` into the function (due to macro creation)
|
||||
// so we pass in each individual value instead
|
||||
match sbot::update_profile_info(
|
||||
data.current_name,
|
||||
data.current_description,
|
||||
data.new_name,
|
||||
data.new_description,
|
||||
data.image,
|
||||
) {
|
||||
Ok(success_msg) => (
|
||||
"flash_name=success".to_string(),
|
||||
format!("flash_msg={}", success_msg),
|
||||
),
|
||||
Err(error_msg) => (
|
||||
"flash_name=error".to_string(),
|
||||
format!("flash_msg={}", error_msg),
|
||||
),
|
||||
}
|
||||
}
|
||||
_ => (
|
||||
"flash_name=warning".to_string(),
|
||||
"Profile is unavailable.".to_string(),
|
||||
),
|
||||
};
|
||||
|
||||
Response::redirect_303("/scuttlebutt/profile/update").add_flash(flash_name, flash_msg)
|
||||
}
|
||||
|
||||
/*
|
||||
match sbot::validate_public_key(&data.public_key) {
|
||||
Ok(_) => {
|
||||
let url = format!("/scuttlebutt/profile?={}", &data.public_key);
|
||||
Response::redirect_303(url)
|
||||
}
|
||||
Err(err) => {
|
||||
let (flash_name, flash_msg) =
|
||||
("flash_name=error".to_string(), format!("flash_msg={}", err));
|
||||
Response::redirect_303("/scuttlebutt/search").add_flash(flash_name, flash_msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
|
@ -4,6 +4,7 @@ use std::{
|
|||
fs,
|
||||
fs::File,
|
||||
io,
|
||||
io::prelude::*,
|
||||
path::Path,
|
||||
process::{Command, Output},
|
||||
};
|
||||
|
@ -12,8 +13,8 @@ use async_std::task;
|
|||
use dirs;
|
||||
use futures::stream::TryStreamExt;
|
||||
use golgi::{api::friends::RelationshipQuery, blobs, messages::SsbMessageValue, Sbot};
|
||||
use log::info;
|
||||
use peach_lib::sbot::SbotConfig;
|
||||
use rouille::input::post::BufferedFile;
|
||||
use temporary::Directory;
|
||||
|
||||
use crate::{error::PeachWebError, utils::sbot};
|
||||
|
@ -168,13 +169,10 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
|||
|
||||
// if an ssb_id has been provided, 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() {
|
||||
let id = if let Some(peer_id) = ssb_id {
|
||||
// we are not dealing with the local profile
|
||||
profile.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(),
|
||||
|
@ -228,7 +226,7 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
|||
|
||||
// determine the path to the blob defined by the value of `profile.image`
|
||||
if let Some(ref blob_id) = profile.image {
|
||||
profile.blob_path = match blobs::get_blob_path(&blob_id) {
|
||||
profile.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
|
||||
|
@ -246,6 +244,84 @@ pub fn get_profile_info(ssb_id: Option<String>) -> Result<Profile, Box<dyn Error
|
|||
})
|
||||
}
|
||||
|
||||
/// Update the profile info for the local public key.
|
||||
///
|
||||
/// Profile info includes name, description and image.
|
||||
pub fn update_profile_info(
|
||||
current_name: String,
|
||||
current_description: String,
|
||||
new_name: Option<String>,
|
||||
new_description: Option<String>,
|
||||
image: Option<BufferedFile>,
|
||||
) -> Result<String, String> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
||||
task::block_on(async {
|
||||
let mut sbot_client = init_sbot_with_config(&sbot_config)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// track whether the name, description or image have been updated
|
||||
let mut name_updated: bool = false;
|
||||
let mut description_updated: bool = false;
|
||||
let mut image_updated: bool = false;
|
||||
|
||||
// check if a new_name value has been submitted in the form
|
||||
if let Some(name) = new_name {
|
||||
// only update the name if it has changed
|
||||
if name != current_name {
|
||||
// TODO: debug!("Publishing new Scuttlebutt profile name");
|
||||
if let Err(e) = sbot_client.publish_name(&name).await {
|
||||
return Err(format!("Failed to update name: {}", e));
|
||||
} else {
|
||||
name_updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(description) = new_description {
|
||||
// only update the description if it has changed
|
||||
if description != current_description {
|
||||
//debug!("Publishing new Scuttlebutt profile description");
|
||||
if let Err(e) = sbot_client.publish_description(&description).await {
|
||||
return Err(format!("Failed to update description: {}", e));
|
||||
} else {
|
||||
description_updated = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only update the image if a file was uploaded
|
||||
if let Some(img) = image {
|
||||
// only write the blob if it has a filename and data > 0 bytes
|
||||
if img.filename.is_some() && !img.data.is_empty() {
|
||||
match write_blob_to_store(img).await {
|
||||
Ok(blob_id) => {
|
||||
// if the file was successfully added to the blobstore,
|
||||
// publish an about image message with the blob id
|
||||
if let Err(e) = sbot_client.publish_image(&blob_id).await {
|
||||
return Err(format!("Failed to update image: {}", e));
|
||||
} else {
|
||||
image_updated = true
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(format!("Failed to add image to blobstore: {}", e)),
|
||||
}
|
||||
} else {
|
||||
image_updated = false
|
||||
}
|
||||
}
|
||||
|
||||
if name_updated || description_updated || image_updated {
|
||||
Ok("Profile updated".to_string())
|
||||
} else {
|
||||
// no updates were made but no errors were encountered either
|
||||
Ok("Profile info unchanged".to_string())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers blocked by the local public key.
|
||||
pub fn get_blocks_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
|
@ -306,13 +382,13 @@ pub fn get_follows_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>
|
|||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = peer_info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(&blob_id) {
|
||||
if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
peer_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 blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists == true => {
|
||||
Ok(exists) if exists => {
|
||||
peer_info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
|
@ -356,13 +432,13 @@ pub fn get_friends_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>
|
|||
// retrieve the profile image blob id for the given peer
|
||||
if let Some(blob_id) = peer_info.get("image") {
|
||||
// look-up the path for the image blob
|
||||
if let Ok(blob_path) = blobs::get_blob_path(&blob_id) {
|
||||
if let Ok(blob_path) = blobs::get_blob_path(blob_id) {
|
||||
// insert the image blob path of the peer into the info hashmap
|
||||
peer_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 sbot::blob_is_stored_locally(&blob_path).await {
|
||||
Ok(exists) if exists == true => {
|
||||
Ok(exists) if exists => {
|
||||
peer_info.insert("blob_exists".to_string(), "true".to_string())
|
||||
}
|
||||
_ => peer_info.insert("blob_exists".to_string(), "false".to_string()),
|
||||
|
@ -401,7 +477,7 @@ pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
|
|||
// 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)?;
|
||||
let mut home_path = dirs::home_dir().ok_or(PeachWebError::HomeDir)?;
|
||||
// add the go-ssb subdirectory
|
||||
home_path.push(".ssb-go");
|
||||
// convert the PathBuf to a String
|
||||
|
@ -422,32 +498,40 @@ pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebErr
|
|||
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<String, PeachWebError> {
|
||||
pub async fn write_blob_to_store(image: BufferedFile) -> Result<String, PeachWebError> {
|
||||
// we performed a `image.filename.is_some()` check before calling `write_blob_to_store`
|
||||
// so it should be safe to do a simple unwrap here
|
||||
let filename = image
|
||||
.filename
|
||||
.expect("retrieving filename from uploaded file");
|
||||
|
||||
// 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?;
|
||||
fs::write(&temp_path, &image.data)?;
|
||||
|
||||
// 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)
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue