//! Web server route handlers. use async_std::channel::Sender; use log::{debug, warn}; use rocket::{form::Form, get, post, response::Redirect, uri, FromForm, State}; use rocket_dyn_templates::{tera::Context, Template}; use crate::{ db::{Database, Peer}, sbot, task_loop::Task, utils, WhoAmI, }; #[derive(FromForm)] pub struct PeerForm { pub public_key: String, } #[get("/")] pub async fn home(db: &State) -> Template { let peers = db.get_peers(); let mut context = Context::new(); let mut peers_unread = Vec::new(); for peer in peers { let unread_count = db.get_unread_post_count(&peer.public_key); peers_unread.push((peer, unread_count.to_string())); } context.insert("peers", &peers_unread); Template::render("base", &context.into_json()) } #[get("/posts///delete")] pub async fn delete_post(db: &State, public_key: &str, msg_id: &str) -> Redirect { // Delete the post from the database. This method cannot panic, so we're // safe to unwrap the result. db.remove_post(public_key, msg_id).unwrap(); Redirect::to(uri!(posts(public_key))) } #[get("/posts/")] pub async fn posts(db: &State, public_key: &str) -> Template { let peers = db.get_peers(); let mut context = Context::new(); let mut peers_unread = Vec::new(); for peer in peers { let unread_count = db.get_unread_post_count(&peer.public_key); peers_unread.push((peer, unread_count.to_string())); } context.insert("peers", &peers_unread); let posts = db.get_posts(public_key).unwrap(); context.insert("selected_peer", &public_key); context.insert("posts", &posts); Template::render("base", &context.into_json()) } #[get("/posts//")] pub async fn post(db: &State, public_key: &str, msg_id: &str) -> Template { let peers = db.get_peers(); let mut context = Context::new(); let mut peers_unread = Vec::new(); for peer in peers { let unread_count = db.get_unread_post_count(&peer.public_key); peers_unread.push((peer, unread_count.to_string())); } context.insert("peers", &peers_unread); let posts = db.get_posts(public_key).unwrap(); let post = db.get_post(public_key, msg_id).unwrap(); context.insert("selected_peer", &public_key); context.insert( "selected_peer_encoded", &uri_encode::encode_uri_component(public_key), ); context.insert("selected_post", &msg_id); context.insert( "selected_post_encoded", &uri_encode::encode_uri_component(msg_id), ); context.insert("posts", &posts); context.insert("post", &post); context.insert("post_is_selected", &true); Template::render("base", &context.into_json()) } #[get("/posts///read")] pub async fn mark_post_read(db: &State, public_key: &str, msg_id: &str) -> Redirect { // Retrieve the post from the database, mark it as read and reinsert it. if let Ok(Some(mut post)) = db.get_post(public_key, msg_id) { post.read = true; db.add_post(public_key, post).unwrap(); } else { warn!( "failed to find post {} authored by {} in database", msg_id, public_key ) } Redirect::to(uri!(post(public_key, msg_id))) } #[get("/posts///unread")] pub async fn mark_post_unread(db: &State, public_key: &str, msg_id: &str) -> Redirect { // Retrieve the post from the database, mark it as unread and reinsert it. if let Ok(Some(mut post)) = db.get_post(public_key, msg_id) { post.read = false; db.add_post(public_key, post).unwrap(); } else { warn!( "failed to find post {} authored by {} in database", msg_id, public_key ) } Redirect::to(uri!(post(public_key, msg_id))) } #[get("/posts/download_latest")] pub async fn download_latest_posts(db: &State, tx: &State>) -> Redirect { for peer in db.get_peers() { // Fetch the latest root posts authored by each peer we're // subscribed to. Posts will be added to the key-value database. if let Err(e) = tx .send(Task::FetchLatestPosts(peer.public_key.clone())) .await { warn!("task loop error: {}", e) } // Fetch the latest name for each peer we're subscribed to and update // the database. if let Err(e) = tx.send(Task::FetchLatestName(peer.public_key)).await { warn!("task loop error: {}", e) } } Redirect::to(uri!(home)) } #[post("/subscribe", data = "")] pub async fn subscribe_form( db: &State, whoami: &State, tx: &State>, peer: Form, ) -> Redirect { if utils::validate_public_key(&peer.public_key).is_ok() { debug!("public key {} is valid", &peer.public_key); // Retrieve the name of the peer to which we are subscribing. let peer_name = match sbot::get_name(&peer.public_key).await { Ok(name) => name, Err(e) => { warn!("failed to fetch name for {}: {}", &peer.public_key, e); String::from("") } }; let peer_info = Peer::new(&peer.public_key).set_name(&peer_name); // Add the peer to the database and then check the follow state. // Follow the peer if our local instance is not already following. if db.add_peer(peer_info).is_ok() { debug!("added {} to peer tree in database", &peer.public_key); match sbot::is_following(&whoami.0, &peer.public_key).await { Ok(status) if status.as_str() == "false" => { match sbot::follow_peer(&peer.public_key).await { Ok(_) => debug!("followed {}", &peer.public_key), Err(e) => warn!("failed to follow {}: {}", &peer.public_key, e), } } Ok(status) if status.as_str() == "true" => { debug!("we already follow {}", &peer.public_key) } _ => (), } let peer_id = peer.public_key.to_string(); // Fetch all root posts authored by the peer we're subscribing // to. Posts will be added to the key-value database. if let Err(e) = tx.send(Task::FetchAllPosts(peer_id)).await { warn!("task loop error: {}", e) } } else { warn!( "failed to add {} to peer tree in database", &peer.public_key ) } } else { warn!("{} is invalid", &peer.public_key); } Redirect::to(uri!(home)) } #[post("/unsubscribe", data = "")] pub fn unsubscribe_form(db: &State, peer: Form) -> Redirect { if let Err(e) = utils::validate_public_key(&peer.public_key) { warn!("{} is invalid: {}", &peer.public_key, e) } else { debug!("public key {} is valid", &peer.public_key); match db.remove_peer(&peer.public_key) { Ok(_) => debug!("removed {} from peer tree in database", &peer.public_key), Err(_e) => warn!( "failed to remove {} from peer tree in database", &peer.public_key ), } } Redirect::to(uri!(home)) }