webcomment/src/server/api.rs

198 lines
5.2 KiB
Rust

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<Notification>,
) {
// 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<tide::Response> {
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::<Vec<resps::CommentWithId>>(),
})
.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<Notification>,
) -> tide::Result<tide::Response> {
let Ok(query) = req.body_json::<queries::NewComment>().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())
}
}
}