webcomment/src/server.rs

261 lines
7 KiB
Rust

use crate::{config::*, db::*, helpers, queries::*, templates::*};
use log::error;
use std::sync::Arc;
use tera::Context;
pub async fn start_server(config: Config, dbs: Dbs, templates: Templates) {
tide::log::start();
let templates = Arc::new(templates);
let config = Arc::new(config);
let mut app = tide::new();
app.at(&format!("{}t/:topic", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
serve_comments(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}t/:topic", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_comments(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at(&format!("{}admin", config.root_url)).get({
let config = config.clone();
let templates = templates.clone();
move |req: tide::Request<()>| serve_admin_login(req, config.clone(), templates.clone())
});
app.at(&format!("{}admin", config.root_url)).post({
let config = config.clone();
let templates = templates.clone();
let dbs = dbs.clone();
move |req: tide::Request<()>| {
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.listen(config.listen).await.unwrap();
}
async fn serve_comments<'a>(
req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
let Ok(topic) = req.param("topic") else {
return Err(tide::Error::from_str(404, "No topic"))
};
let admin = req.cookie("admin").map_or(false, |psw| {
config.admin_passwords.contains(&String::from(psw.value()))
});
let topic_hash = TopicHash::from_topic(topic);
let mut context = Context::new();
context.insert("config", &config);
context.insert("admin", &admin);
if admin {
if let Ok(query) = req.query::<ApproveQuery>() {
if let Ok(comment_id) = CommentId::from_base64(&query.approve) {
helpers::approve_comment(comment_id, &dbs)
.map_err(|e| error!("Approving comment: {:?}", e))
.ok();
}
}
if let Ok(query) = req.query::<RemoveQuery>() {
if let Ok(comment_id) = CommentId::from_base64(&query.remove) {
helpers::remove_pending_comment(comment_id, &dbs)
.map_err(|e| error!("Removing comment: {:?}", e))
.ok();
}
}
context.insert(
"comments_pending",
&helpers::iter_pending_comments_by_topic(topic_hash.clone(), &dbs)
.map(|(comment_id, comment)| CommentWithId {
author: comment.author,
id: comment_id.to_base64(),
needs_approval: true,
post_time: comment.post_time,
text: comment.text,
})
.collect::<Vec<CommentWithId>>(),
);
}
context.insert(
"comments",
&helpers::iter_approved_comments_by_topic(topic_hash, &dbs)
.map(|(comment_id, comment)| CommentWithId {
author: comment.author,
id: comment_id.to_base64(),
needs_approval: false,
post_time: comment.post_time,
text: comment.text,
})
.collect::<Vec<CommentWithId>>(),
);
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(templates.tera.render("comments.html", &context)?)
.build())
}
async fn serve_admin<'a>(
_req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
let mut context = Context::new();
context.insert("config", &config);
context.insert("admin", &true);
context.insert(
"comments",
&dbs.comment_pending
.iter()
.filter_map(|entry| {
let ((_topic_hash, _time, comment_id), ()) = dbg!(entry
.map_err(|e| error!("Reading comment_pending: {:?}", e))
.ok()?);
let comment = dbs
.comment
.get(&comment_id)
.map_err(|e| error!("Reading comment: {:?}", e))
.ok()?
.or_else(|| {
error!("Comment not found");
None
})?;
Some(CommentWithId {
author: comment.author,
id: comment_id.to_base64(),
needs_approval: true,
post_time: comment.post_time,
text: comment.text,
})
})
.collect::<Vec<CommentWithId>>(),
);
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(templates.tera.render("comments.html", &context)?)
.build())
}
async fn serve_admin_login(
_req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
) -> tide::Result<tide::Response> {
let mut context = Context::new();
context.insert("config", &config);
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(templates.tera.render("admin_login.html", &context)?)
.build())
}
async fn handle_post_comments(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
match req.body_form::<CommentQuery>().await? {
CommentQuery::NewComment(query) => {
let Ok(topic) = req.param("topic") else {
return Err(tide::Error::from_str(404, "No topic"))
};
if query.author.len() > config.comment_author_max_len {
return Err(tide::Error::from_str(400, "Too long"));
}
if query.email.len() > config.comment_email_max_len {
return Err(tide::Error::from_str(400, "Too long"));
}
if query.text.len() > config.comment_text_max_len {
return Err(tide::Error::from_str(400, "Too long"));
}
let topic_hash = TopicHash::from_topic(topic);
let time = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let comment = Comment {
topic_hash,
author: query.author,
email: if query.email.is_empty() {
None
} else {
Some(query.email)
},
last_edit_time: None,
post_time: time,
text: query.text,
};
helpers::new_pending_comment(&comment, &dbs)
.map_err(|e| error!("Adding pending comment: {:?}", e))
.ok();
}
_ => todo!(),
}
serve_comments(req, config, templates, dbs).await
}
async fn handle_post_admin(
mut req: tide::Request<()>,
config: Arc<Config>,
templates: Arc<Templates>,
dbs: Dbs,
) -> tide::Result<tide::Response> {
if let Some(psw) = req.cookie("admin") {
if config.admin_passwords.contains(&String::from(psw.value())) {
match req.body_form::<AdminQuery>().await? {
_ => serve_admin(req, config, templates, dbs).await,
}
} else {
serve_admin_login(req, config, templates).await
}
} else if let AdminQuery::Login(query) = req.body_form::<AdminQuery>().await? {
if config.admin_passwords.contains(&query.psw) {
serve_admin(req, config.clone(), templates, dbs)
.await
.map(|mut r| {
let mut cookie = tide::http::Cookie::new("admin", query.psw);
cookie.set_http_only(Some(true));
cookie.set_path(config.root_url.clone());
if let Some(domain) = &config.cookies_domain {
cookie.set_domain(domain.clone());
}
if config.cookies_https_only {
cookie.set_secure(Some(true));
}
r.insert_cookie(cookie);
r
})
} else {
serve_admin_login(req, config, templates).await
}
} else {
serve_admin_login(req, config, templates).await
}
}