add search and invite templates and route handlers
This commit is contained in:
parent
112cfca67b
commit
34b4cbff32
|
@ -106,10 +106,28 @@ pub fn mount_peachpub_routes(request: &Request) -> Response {
|
|||
Response::html(routes::scuttlebutt::friends::build_template())
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/invites) => {
|
||||
Response::html(routes::scuttlebutt::invites::build_template(request))
|
||||
.reset_flash()
|
||||
},
|
||||
|
||||
(POST) (/scuttlebutt/invites) => {
|
||||
routes::scuttlebutt::invites::handle_form(request)
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/peers) => {
|
||||
Response::html(routes::scuttlebutt::peers::build_template())
|
||||
},
|
||||
|
||||
(GET) (/scuttlebutt/search) => {
|
||||
Response::html(routes::scuttlebutt::search::build_template(request))
|
||||
.reset_flash()
|
||||
},
|
||||
|
||||
(POST) (/scuttlebutt/search) => {
|
||||
routes::scuttlebutt::search::handle_form(request)
|
||||
},
|
||||
|
||||
(GET) (/status/scuttlebutt) => {
|
||||
Response::html(routes::status::scuttlebutt::build_template())
|
||||
},
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
use maud::{html, Markup, PreEscaped};
|
||||
use peach_lib::sbot::SbotStatus;
|
||||
use rouille::{post_input, try_or_400, Request, Response};
|
||||
|
||||
use crate::{
|
||||
templates,
|
||||
utils::{
|
||||
flash::{FlashRequest, FlashResponse},
|
||||
sbot,
|
||||
},
|
||||
};
|
||||
|
||||
// ROUTE: /scuttlebutt/invites
|
||||
|
||||
/// Render the invite form template.
|
||||
fn invite_form_template(
|
||||
flash_name: Option<&str>,
|
||||
flash_msg: Option<&str>,
|
||||
invite_code: Option<&str>,
|
||||
) -> Markup {
|
||||
html! {
|
||||
(PreEscaped("<!-- SCUTTLEBUTT INVITE FORM -->"))
|
||||
div class="card center" {
|
||||
form id="invites" class="center" action="/scuttlebutt/invites" method="post" {
|
||||
div class="center" style="width: 80%;" {
|
||||
label for="inviteUses" class="label-small font-gray" title="Number of times the invite code can be reused" { "USES" }
|
||||
input type="number" id="inviteUses" name="uses" min="1" max="150" size="3" value="1";
|
||||
@if let Some(code) = invite_code {
|
||||
p class="card-text" style="margin-top: 1rem; user-select: all;" title="Invite code" {
|
||||
(code)
|
||||
}
|
||||
}
|
||||
}
|
||||
(PreEscaped("<!-- BUTTONS -->"))
|
||||
input id="createInvite" class="button button-primary center" style="margin-top: 1rem;" type="submit" title="Create a new invite code" value="Create";
|
||||
a id="cancel" class="button button-secondary center" href="/scuttlebutt/peers" title="Cancel" { "Cancel" }
|
||||
}
|
||||
// render flash message if cookies were found in the request
|
||||
@if let (Some(name), Some(msg)) = (flash_name, flash_msg) {
|
||||
// avoid displaying the invite code-containing flash msg
|
||||
@if name != "code" {
|
||||
(PreEscaped("<!-- FLASH MESSAGE -->"))
|
||||
(templates::flash::build_template(&name, &msg))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Scuttlebutt invite template builder.
|
||||
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();
|
||||
|
||||
// if flash_name is "code" then flash_msg will be an invite code
|
||||
let invite_code = if flash_name == Some("code") {
|
||||
flash_msg
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let invite_form_template = match SbotStatus::read() {
|
||||
// only render the invite form template if the sbot is active
|
||||
Ok(status) if status.state == Some("active".to_string()) => {
|
||||
html! { (invite_form_template(flash_name, flash_msg, invite_code)) }
|
||||
}
|
||||
_ => {
|
||||
// the sbot is not active; render a message instead of the invite form
|
||||
templates::inactive::build_template("Invite creation is unavailable.")
|
||||
}
|
||||
};
|
||||
|
||||
let body =
|
||||
templates::nav::build_template(invite_form_template, "Invites", Some("/scuttlebutt/peers"));
|
||||
|
||||
templates::base::build_template(body)
|
||||
}
|
||||
|
||||
/// Parse the invite uses data and attempt to generate an invite code.
|
||||
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, {
|
||||
// the number of times the invite code can be used
|
||||
uses: u16,
|
||||
}));
|
||||
|
||||
let (flash_name, flash_msg) = match sbot::create_invite(data.uses) {
|
||||
Ok(code) => ("flash_name=code".to_string(), format!("flash_msg={}", code)),
|
||||
Err(e) => ("flash_name=error".to_string(), format!("flash_msg={}", e)),
|
||||
};
|
||||
|
||||
Response::redirect_303("/scuttlebutt/invites").add_flash(flash_name, flash_msg)
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
pub mod blocks;
|
||||
pub mod follows;
|
||||
pub mod friends;
|
||||
pub mod invites;
|
||||
pub mod peers;
|
||||
pub mod search;
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
use maud::{html, PreEscaped};
|
||||
use rouille::{post_input, try_or_400, Request, Response};
|
||||
|
||||
use crate::{
|
||||
templates,
|
||||
utils::{
|
||||
flash::{FlashRequest, FlashResponse},
|
||||
sbot,
|
||||
},
|
||||
};
|
||||
|
||||
// ROUTE: /scuttlebutt/search
|
||||
|
||||
/// Scuttlebutt peer search template builder.
|
||||
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 search_template = html! {
|
||||
(PreEscaped("<!-- PEER SEARCH FORM -->"))
|
||||
div class="card center" {
|
||||
form id="sbotConfig" class="center" action="/scuttlebutt/search" method="post" {
|
||||
div class="center" style="display: flex; flex-direction: column; margin-bottom: 2rem;" title="Public key (ID) of a peer" {
|
||||
label for="publicKey" class="label-small font-gray" { "PUBLIC KEY" }
|
||||
input type="text" id="publicKey" name="public_key" placeholder="@xYz...=.ed25519" autofocus;
|
||||
}
|
||||
(PreEscaped("<!-- BUTTONS -->"))
|
||||
input id="search" class="button button-primary center" type="submit" title="Search for peer" value="Search";
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let body =
|
||||
templates::nav::build_template(search_template, "Search", Some("/scuttlebutt/peers"));
|
||||
|
||||
templates::base::build_template(body)
|
||||
}
|
||||
|
||||
/// Parse the public key, verify that it's valid and then redirect to the
|
||||
/// profile of the given key.
|
||||
///
|
||||
/// If the public key is invalid, set an error flash message and redirect.
|
||||
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, {
|
||||
public_key: String,
|
||||
}));
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -46,65 +46,45 @@ pub async fn init_sbot_with_config(
|
|||
Ok(sbot_client)
|
||||
}
|
||||
|
||||
// FILEPATH FUNCTIONS
|
||||
// return the path of the ssb-go directory
|
||||
pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
|
||||
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)?
|
||||
// SCUTTLEBUTT FUNCTIONS
|
||||
|
||||
/// Ensure that the given public key is a valid ed25519 key.
|
||||
///
|
||||
/// Return an error string if the key is invalid.
|
||||
pub fn validate_public_key(public_key: &str) -> Result<(), String> {
|
||||
// ensure the id starts with the correct sigil link
|
||||
if !public_key.starts_with('@') {
|
||||
return Err("Invalid key: expected '@' sigil as first character".to_string());
|
||||
}
|
||||
|
||||
// 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("Invalid key: no dot index was found".to_string()),
|
||||
};
|
||||
Ok(go_ssb_path)
|
||||
|
||||
// check hashing algorithm (must end with ".ed25519")
|
||||
if !&public_key.ends_with(".ed25519") {
|
||||
return Err("Invalid key: hashing algorithm must be ed25519".to_string());
|
||||
}
|
||||
|
||||
// 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("Invalid key: base64 data length is incorrect".to_string());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// check whether a blob is in the blobstore
|
||||
pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebError> {
|
||||
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<String, PeachWebError> {
|
||||
// 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)
|
||||
}
|
||||
*/
|
||||
|
||||
/// Calculate the latest sequence number for the local profile.
|
||||
///
|
||||
/// Retrieves a list of all messages authored by the local public key,
|
||||
/// reverses the list and reads the sequence number of the most recently
|
||||
/// authored message. This gives us the size of the database in terms of
|
||||
/// the total number of locally-authored messages.
|
||||
pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
@ -126,6 +106,21 @@ pub fn latest_sequence_number() -> Result<u64, Box<dyn Error>> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn create_invite(uses: u16) -> Result<String, Box<dyn Error>> {
|
||||
// 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?;
|
||||
|
||||
//TODO: debug!("Generating Scuttlebutt invite code");
|
||||
let invite_code = sbot_client.invite_create(uses).await?;
|
||||
|
||||
Ok(invite_code)
|
||||
})
|
||||
}
|
||||
|
||||
/// 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
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
@ -160,6 +155,7 @@ pub fn get_blocks_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>>
|
|||
})
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers followed by the local public key.
|
||||
pub fn get_follows_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
@ -207,6 +203,7 @@ pub fn get_follows_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>
|
|||
})
|
||||
}
|
||||
|
||||
/// Retrieve a list of peers friended by the local public key.
|
||||
pub fn get_friends_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>> {
|
||||
// retrieve latest go-sbot configuration parameters
|
||||
let sbot_config = SbotConfig::read().ok();
|
||||
|
@ -268,3 +265,63 @@ pub fn get_friends_list() -> Result<Vec<HashMap<String, String>>, Box<dyn Error>
|
|||
Ok(peer_list)
|
||||
})
|
||||
}
|
||||
|
||||
// FILEPATH FUNCTIONS
|
||||
|
||||
/// Return the path of the ssb-go directory.
|
||||
pub fn get_go_ssb_path() -> Result<String, PeachWebError> {
|
||||
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)
|
||||
}
|
||||
|
||||
/// Check whether a blob is in the blobstore.
|
||||
pub async fn blob_is_stored_locally(blob_path: &str) -> Result<bool, PeachWebError> {
|
||||
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<String, PeachWebError> {
|
||||
// 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)
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue