mod queries; mod resps; use crate::{config::*, db::*, helpers, notify::Notification}; use crossbeam_channel::Sender; use log::{error, warn}; pub async fn init_routes( app: &mut tide::Server<()>, config: &'static Config, dbs: Dbs, notify_send: Sender, ) { // TODO pagination app.at(&format!("{}api/comments_by_topic", config.root_url)) .post({ let dbs = dbs.clone(); move |req: tide::Request<()>| query_comments_by_topic(req, config, dbs.clone()) }); app.at(&format!("{}api/new_comment", config.root_url)) .post({ move |req: tide::Request<()>| { query_new_comment(req, config, dbs.clone(), notify_send.clone()) } }); } async fn query_comments_by_topic( mut req: tide::Request<()>, config: &Config, dbs: Dbs, ) -> tide::Result { let Ok(queries::CommentsByTopic { mutation_token: _mutation_token, topic, }) = req.body_json().await else { return Ok(tide::Response::builder(400) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body( tide::Body::from_json(&resps::Error::InvalidRequest).unwrap(), ) .build()); }; let topic_hash = TopicHash::from_topic(&topic); Ok(tide::Response::builder(200) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body( tide::Body::from_json(&resps::CommentsByTopic { comments: helpers::iter_approved_comments_by_topic(topic_hash, &dbs) .map( |(comment_id, comment, _comment_status)| resps::CommentWithId { addr: None, author: comment.author, editable: false, id: comment_id.to_base64(), last_edit_time: comment.last_edit_time, post_time: comment.post_time, status: None, text: comment.text, }, ) .collect::>(), }) .map_err(|e| { error!("Serializing CommentsByTopicResp to json: {e:?}"); tide::Error::from_str(500, "Internal server error") })?, ) .build()) } async fn query_new_comment( mut req: tide::Request<()>, config: &Config, dbs: Dbs, notify_send: Sender, ) -> tide::Result { let Ok(query) = req.body_json::().await else { return Ok(tide::Response::builder(400) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body( tide::Body::from_json(&resps::Error::InvalidRequest).unwrap(), ) .build()); }; if query.author.len() > config.comment_author_max_len || query.email.len() > config.comment_email_max_len || query.text.len() > config.comment_text_max_len { return Ok(tide::Response::builder(400) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body(tide::Body::from_json(&resps::Error::IllegalContent).unwrap()) .build()); } let client_addr = match helpers::get_client_addr(config, &req) { Some(Ok(addr)) => Some(addr), Some(Err(e)) => { warn!("Unable to parse client addr: {}", e); None } None => { warn!("No client addr"); None } }; let antispam_enabled = config.antispam_enable && client_addr .as_ref() .map_or(false, |addr| !config.antispam_whitelist.contains(addr)); if let Some(client_addr) = &client_addr { if antispam_enabled { if let Some(antispam_timeout) = helpers::antispam_check_client_mutation(client_addr, &dbs, config).unwrap() { return Ok(tide::Response::builder(403) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body( tide::Body::from_json(&resps::Error::Antispam { timeout: antispam_timeout, }) .unwrap(), ) .build()); } } } // It's OK if let Some(client_addr) = &client_addr { if antispam_enabled { helpers::antispam_update_client_mutation(client_addr, &dbs).unwrap(); } } let topic_hash = TopicHash::from_topic(&query.topic); let time = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .as_secs(); let comment = Comment { topic_hash, author: if query.author.is_empty() { petname::Petnames::large().generate_one(2, " ") } else { query.author }, email: if query.email.is_empty() { None } else { Some(query.email) }, last_edit_time: None, mutation_token: MutationToken::new(), post_time: time, text: query.text, }; match helpers::new_pending_comment(&comment, client_addr, &dbs) { Ok(comment_id) => { notify_send.send(Notification { topic: query.topic }).ok(); Ok(tide::Response::builder(200) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body( tide::Body::from_json(&resps::NewComment { id: comment_id.to_base64(), mutation_token: comment.mutation_token.to_base64(), post_time: time, }) .unwrap(), ) .build()) } // TODO add message to client log and change http code Err(e) => { error!("Adding pending comment: {:?}", e); Ok(tide::Response::builder(500) .content_type(tide::http::mime::JSON) .header("Access-Control-Allow-Origin", &config.cors_allow_origin) .body(tide::Body::from_json(&resps::Error::Internal).unwrap()) .build()) } } }