284 lines
7.4 KiB
Rust
284 lines
7.4 KiB
Rust
use crate::{config::*, db::*, helpers, notify::Notification, server::check_admin_password};
|
|
use webcomment_common::{api::*, types::*};
|
|
|
|
use crossbeam_channel::Sender;
|
|
use log::{error, warn};
|
|
use serde::Serialize;
|
|
|
|
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/admin/comments_by_topic", config.root_url))
|
|
.post({
|
|
let dbs = dbs.clone();
|
|
move |req: tide::Request<()>| query_comments_by_topic_admin(req, config, dbs.clone())
|
|
});
|
|
app.at(&format!("{}api/admin/remove_comment", config.root_url))
|
|
.post({
|
|
let dbs = dbs.clone();
|
|
move |req: tide::Request<()>| query_remove_comment_admin(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())
|
|
}
|
|
});
|
|
}
|
|
|
|
fn build_resp<S, B, E>(config: &Config, status: S, body: B) -> Result<tide::Response, E>
|
|
where
|
|
S: TryInto<tide::StatusCode>,
|
|
S::Error: std::fmt::Debug,
|
|
B: Serialize,
|
|
{
|
|
Ok(tide::Response::builder(status)
|
|
.content_type(tide::http::mime::JSON)
|
|
.header("Access-Control-Allow-Origin", &config.cors_allow_origin)
|
|
.body(tide::Body::from_json(&body).unwrap())
|
|
.build())
|
|
}
|
|
|
|
// TODO using mutation_token:
|
|
// * add pending comments
|
|
// * add status
|
|
// * add email
|
|
// * add editable
|
|
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 build_resp(config, 400, resps::Result::Err(resps::Error::InvalidRequest));
|
|
};
|
|
|
|
let topic_hash = TopicHash::from_topic(&topic);
|
|
|
|
build_resp(
|
|
config,
|
|
200,
|
|
resps::CommentsByTopic {
|
|
approved_comments: helpers::iter_approved_comments_by_topic(topic_hash, &dbs)
|
|
.map(
|
|
|(comment_id, comment, _comment_status)| resps::ApprovedCommentWithMeta {
|
|
editable: false,
|
|
email: None,
|
|
author: comment.author,
|
|
id: comment_id.to_base64(),
|
|
last_edit_time: comment.last_edit_time,
|
|
post_time: comment.post_time,
|
|
status: CommentStatus::Approved,
|
|
text: comment.text,
|
|
},
|
|
)
|
|
.collect::<Vec<resps::ApprovedCommentWithMeta>>(),
|
|
},
|
|
)
|
|
}
|
|
|
|
async fn query_comments_by_topic_admin(
|
|
mut req: tide::Request<()>,
|
|
config: &Config,
|
|
dbs: Dbs,
|
|
) -> tide::Result<tide::Response> {
|
|
let Ok(queries::CommentsByTopicAdmin {admin_psw,
|
|
topic,
|
|
}) = req.body_json().await else {
|
|
return build_resp(config, 400, resps::Result::Err(resps::Error::InvalidRequest));
|
|
};
|
|
|
|
if check_admin_password(config, &admin_psw).is_none() {
|
|
return build_resp(config, 403, resps::Result::Err(resps::Error::BadAdminAuth));
|
|
}
|
|
|
|
let topic_hash = TopicHash::from_topic(&topic);
|
|
|
|
build_resp(
|
|
config,
|
|
200,
|
|
resps::CommentsByTopicAdmin {
|
|
approved_comments: helpers::iter_approved_comments_by_topic(topic_hash.clone(), &dbs)
|
|
.map(
|
|
|(comment_id, comment, comment_status)| resps::ApprovedCommentWithMeta {
|
|
editable: true,
|
|
email: comment.email,
|
|
author: comment.author,
|
|
id: comment_id.to_base64(),
|
|
last_edit_time: comment.last_edit_time,
|
|
post_time: comment.post_time,
|
|
status: comment_status,
|
|
text: comment.text,
|
|
},
|
|
)
|
|
.collect::<Vec<resps::ApprovedCommentWithMeta>>(),
|
|
pending_comments: helpers::iter_pending_comments_by_topic(topic_hash, &dbs)
|
|
.map(
|
|
|(comment_id, comment, addr, comment_status)| resps::PendingCommentWithMeta {
|
|
addr: addr.as_ref().map(std::net::IpAddr::to_string),
|
|
editable: true,
|
|
email: comment.email,
|
|
author: comment.author,
|
|
id: comment_id.to_base64(),
|
|
last_edit_time: comment.last_edit_time,
|
|
post_time: comment.post_time,
|
|
status: comment_status,
|
|
text: comment.text,
|
|
},
|
|
)
|
|
.collect::<Vec<resps::PendingCommentWithMeta>>(),
|
|
},
|
|
)
|
|
}
|
|
|
|
async fn query_remove_comment_admin(
|
|
mut req: tide::Request<()>,
|
|
config: &Config,
|
|
dbs: Dbs,
|
|
) -> tide::Result<tide::Response> {
|
|
let Ok(queries::RemoveCommentAdmin {admin_psw,
|
|
comment_id,
|
|
}) = req.body_json().await else {
|
|
return build_resp(config, 400, resps::Result::Err(resps::Error::InvalidRequest));
|
|
};
|
|
|
|
if check_admin_password(config, &admin_psw).is_none() {
|
|
return build_resp(config, 403, resps::Result::Err(resps::Error::BadAdminAuth));
|
|
}
|
|
|
|
let Ok(comment_id) = CommentId::from_base64(&comment_id) else {
|
|
return build_resp(
|
|
config,
|
|
400,
|
|
resps::Result::Err(resps::Error::InvalidRequest),
|
|
);
|
|
};
|
|
|
|
match helpers::remove_comment(comment_id, &dbs) {
|
|
Ok(_) => build_resp(config, 200, resps::Result::Ok(resps::Response::Ok)),
|
|
Err(e) => build_resp(
|
|
config,
|
|
200,
|
|
resps::Result::Err(resps::Error::Message(e.to_string())),
|
|
),
|
|
}
|
|
}
|
|
|
|
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 build_resp(config, 400, resps::Result::Err(resps::Error::InvalidRequest));
|
|
};
|
|
|
|
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 build_resp(
|
|
config,
|
|
400,
|
|
resps::Result::Err(resps::Error::IllegalContent),
|
|
);
|
|
}
|
|
|
|
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 build_resp(
|
|
config,
|
|
403,
|
|
resps::Result::Err(resps::Error::Antispam {
|
|
timeout: antispam_timeout,
|
|
}),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
build_resp(
|
|
config,
|
|
200,
|
|
resps::NewComment {
|
|
id: comment_id.to_base64(),
|
|
mutation_token: comment.mutation_token.to_base64(),
|
|
post_time: time,
|
|
},
|
|
)
|
|
}
|
|
// TODO add message to client log and change http code
|
|
Err(e) => {
|
|
error!("Adding pending comment: {:?}", e);
|
|
build_resp(config, 500, resps::Result::Err(resps::Error::Internal))
|
|
}
|
|
}
|
|
}
|