use super::JustStringID; use super::{ fetch_base_data, for_client, get_cookie_map_for_headers, get_cookie_map_for_req, html_response, res_to_error, CookieMap, }; use crate::components::{ Comment, CommunityLink, Content, HTPage, IconExt, MaybeFillTextArea, TimeAgo, UserLink, }; use crate::resp_types::{JustUser, RespCommunityInfoMaybeYour, RespList, RespPostInfo}; use crate::util::author_is_me; use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; async fn page_post( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let cookies = get_cookie_map_for_req(&req)?; page_post_inner(post_id, req.headers(), &cookies, ctx, None, None).await } async fn page_post_inner( post_id: i64, headers: &hyper::header::HeaderMap, cookies: &CookieMap<'_>, ctx: Arc, display_error: Option, prev_values: Option<&HashMap, serde_json::Value>>, ) -> Result, crate::Error> { let lang = crate::get_lang_for_headers(headers); let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?; let api_res = res_to_error( ctx.http_client .request(for_client( hyper::Request::get(format!( "{}/api/unstable/posts/{}{}", ctx.backend_host, post_id, if base_data.login.is_some() { "?include_your=true" } else { "" }, )) .body(Default::default())?, headers, &cookies, )?) .await?, ) .await?; let api_res = hyper::body::to_bytes(api_res.into_body()).await?; let post: RespPostInfo = serde_json::from_slice(&api_res)?; let is_community_moderator = if base_data.login.is_some() { let api_res = res_to_error( ctx.http_client .request(for_client( hyper::Request::get(format!( "{}/api/unstable/communities/{}?include_your=true", ctx.backend_host, post.as_ref().community.id, )) .body(Default::default())?, headers, &cookies, )?) .await?, ) .await?; let api_res = hyper::body::to_bytes(api_res.into_body()).await?; let info: RespCommunityInfoMaybeYour = serde_json::from_slice(&api_res)?; info.you_are_moderator.unwrap() } else { false }; let title = post.as_ref().as_ref().title.as_ref(); Ok(html_response(render::html! { { if post.approved { None } else { Some(render::rsx! {
{lang.tr("post_not_approved", None)}
}) } }

{title}

{ if base_data.login.is_some() { Some(if post.your_vote.is_some() { render::rsx! { <>
{" "} } } else { render::rsx! { <>
{" "} } }) } else { None } } {lang.tr("score", Some(&fluent::fluent_args!["score" => post.score]))} {" "} { if is_community_moderator { Some(if post.approved { render::rsx! {
} } else { render::rsx! {
} }) } else { None } }

{lang.tr("submitted", None)} {" "} {" "}{lang.tr("by", None)}{" "} {" "}{lang.tr("to", None)}{" "}

{ match &post.as_ref().href { None => None, Some(href) => { Some(render::rsx! {

{href.as_ref()}

}) } } } { if author_is_me(&post.as_ref().author, &base_data.login) || (post.local && base_data.is_site_admin()) { Some(render::rsx! {

{lang.tr("delete", None)}

}) } else { None } }

{lang.tr("comments", None)}

{ display_error.map(|msg| { render::rsx! {
{msg}
} }) } { if base_data.login.is_some() { Some(render::rsx! {
}) } else { None } }
    { post.replies.iter().map(|comment| { render::rsx! { } }).collect::>() }
})) } async fn page_post_delete( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let lang = crate::get_lang_for_req(&req); let cookies = get_cookie_map_for_req(&req)?; let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?; let api_res = res_to_error( ctx.http_client .request(for_client( hyper::Request::get(format!( "{}/api/unstable/posts/{}", ctx.backend_host, post_id )) .body(Default::default())?, req.headers(), &cookies, )?) .await?, ) .await?; let api_res = hyper::body::to_bytes(api_res.into_body()).await?; let post: RespPostInfo = serde_json::from_slice(&api_res)?; Ok(html_response(render::html! {

{post.as_ref().as_ref().title.as_ref()}

{lang.tr("post_delete_question", None)}

{lang.tr("no_cancel", None)} {" "}
})) } async fn handler_post_delete_confirm( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let cookies = get_cookie_map_for_req(&req)?; res_to_error( ctx.http_client .request(for_client( hyper::Request::delete(format!( "{}/api/unstable/posts/{}", ctx.backend_host, post_id, )) .body("".into())?, req.headers(), &cookies, )?) .await?, ) .await?; Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) .header(hyper::header::LOCATION, "/") .body("Successfully deleted.".into())?) } async fn handler_post_like( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let cookies = get_cookie_map_for_req(&req)?; res_to_error( ctx.http_client .request(for_client( hyper::Request::put(format!( "{}/api/unstable/posts/{}/your_vote", ctx.backend_host, post_id )) .body("{}".into())?, req.headers(), &cookies, )?) .await?, ) .await?; Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) .header(hyper::header::LOCATION, format!("/posts/{}", post_id)) .body("Successfully liked.".into())?) } async fn page_post_likes( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let lang = crate::get_lang_for_req(&req); let cookies = get_cookie_map_for_req(&req)?; let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?; let api_res = res_to_error( ctx.http_client .request(for_client( hyper::Request::get(format!( "{}/api/unstable/posts/{}/votes", ctx.backend_host, post_id, )) .body(Default::default())?, req.headers(), &cookies, )?) .await?, ) .await?; let api_res = hyper::body::to_bytes(api_res.into_body()).await?; let api_res: RespList = serde_json::from_slice(&api_res)?; Ok(html_response(render::html! { { if api_res.items.is_empty() { Some(render::rsx! {

{lang.tr("post_likes_nothing", None)}

}) } else { None } } { if api_res.items.is_empty() { None } else { Some(render::rsx! { <>

{lang.tr("liked_by", None)}

    { api_res.items.iter().map(|like| { render::rsx! {
  • } }) .collect::>() } { if api_res.next_page.is_some() { Some(render::rsx! {
  • {lang.tr("and_more", None)}
  • }) } else { None } }
}) } }
})) } async fn handler_post_unlike( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let cookies = get_cookie_map_for_req(&req)?; res_to_error( ctx.http_client .request(for_client( hyper::Request::delete(format!( "{}/api/unstable/posts/{}/your_vote", ctx.backend_host, post_id )) .body(Default::default())?, req.headers(), &cookies, )?) .await?, ) .await?; Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) .header(hyper::header::LOCATION, format!("/posts/{}", post_id)) .body("Successfully unliked.".into())?) } async fn handler_post_submit_reply( params: (i64,), ctx: Arc, req: hyper::Request, ) -> Result, crate::Error> { let (post_id,) = params; let (req_parts, body) = req.into_parts(); let lang = crate::get_lang_for_headers(&req_parts.headers); let cookies = get_cookie_map_for_headers(&req_parts.headers)?; let content_type = req_parts .headers .get(hyper::header::CONTENT_TYPE) .ok_or_else(|| { crate::Error::InternalStr("missing content-type header in form submission".to_owned()) })?; let content_type = std::str::from_utf8(content_type.as_ref())?; let boundary = multer::parse_boundary(&content_type)?; let mut multipart = multer::Multipart::new(body, boundary); let mut body_values: HashMap, serde_json::Value> = HashMap::new(); { let mut error = None; loop { let field = multipart.next_field().await?; let field = match field { None => break, Some(field) => field, }; if field.name().is_none() { continue; } if field.name().unwrap() == "attachment_media" { use futures_util::StreamExt; let mut stream = field.peekable(); let first_chunk = std::pin::Pin::new(&mut stream).peek().await; let is_empty = match first_chunk { None => true, Some(Ok(chunk)) => chunk.is_empty(), Some(Err(err)) => { return Err(crate::Error::InternalStr(format!( "failed parsing form: {:?}", err ))); } }; if is_empty { continue; } match stream.get_ref().content_type() { None => { error = Some( lang.tr("comment_reply_attachment_missing_content_type", None) .into_owned(), ); } Some(mime) => { let res = res_to_error( ctx.http_client .request(for_client( hyper::Request::post(format!( "{}/api/unstable/media", ctx.backend_host, )) .header(hyper::header::CONTENT_TYPE, mime.as_ref()) .body(hyper::Body::wrap_stream(stream))?, &req_parts.headers, &cookies, )?) .await?, ) .await; match res { Err(crate::Error::RemoteError((_, message))) => { error = Some(message); } Err(other) => { return Err(other); } Ok(res) => { let res = hyper::body::to_bytes(res.into_body()).await?; let res: JustStringID = serde_json::from_slice(&res)?; body_values.insert( "attachment".into(), format!("local-media://{}", res.id).into(), ); } } println!("finished media upload"); } } } else { let name = field.name().unwrap().to_owned(); let value = field.text().await?; body_values.insert(name.into(), value.into()); } } if let Some(error) = error { return page_post_inner( post_id, &req_parts.headers, &cookies, ctx, Some(error), Some(&body_values), ) .await; } } let api_res = res_to_error( ctx.http_client .request(for_client( hyper::Request::post(format!( "{}/api/unstable/posts/{}/replies", ctx.backend_host, post_id )) .body(serde_json::to_vec(&body_values)?.into())?, &req_parts.headers, &cookies, )?) .await?, ) .await; match api_res { Err(crate::Error::RemoteError((_, message))) => { page_post_inner( post_id, &req_parts.headers, &cookies, ctx, Some(message), Some(&body_values), ) .await } Err(other) => Err(other), Ok(_) => Ok(hyper::Response::builder() .status(hyper::StatusCode::SEE_OTHER) .header(hyper::header::LOCATION, format!("/posts/{}", post_id)) .body("Successfully posted.".into())?), } } pub fn route_posts() -> crate::RouteNode<()> { crate::RouteNode::new().with_child_parse::( crate::RouteNode::new() .with_handler_async("GET", page_post) .with_child( "delete", crate::RouteNode::new() .with_handler_async("GET", page_post_delete) .with_child( "confirm", crate::RouteNode::new() .with_handler_async("POST", handler_post_delete_confirm), ), ) .with_child( "like", crate::RouteNode::new().with_handler_async("POST", handler_post_like), ) .with_child( "likes", crate::RouteNode::new().with_handler_async("GET", page_post_likes), ) .with_child( "unlike", crate::RouteNode::new().with_handler_async("POST", handler_post_unlike), ) .with_child( "submit_reply", crate::RouteNode::new().with_handler_async("POST", handler_post_submit_reply), ), ) }