use serde_derive::Deserialize;
use std::borrow::Cow;
use std::sync::Arc;
use crate::components::{
Comment, Content, HTPage, IconExt, MaybeFillInput, MaybeFillTextArea, NotificationItem,
PostItem, ThingItem, UserLink,
};
use crate::resp_types::{
RespCommentInfo, RespInstanceInfo, RespNotification, RespPostCommentInfo, RespPostListPost,
RespThingInfo, RespUserInfo,
};
use crate::util::author_is_me;
use crate::PageBaseData;
mod communities;
mod posts;
mod r#static;
const COOKIE_AGE: u32 = 60 * 60 * 24 * 365;
#[derive(Deserialize)]
struct ReturnToParams<'a> {
return_to: Option>,
}
type CookieMap<'a> = std::collections::HashMap<&'a str, ginger::Cookie<'a>>;
fn get_cookie_map(src: Option<&str>) -> Result {
match src {
None => Ok(Default::default()),
Some(src) => {
use fallible_iterator::FallibleIterator;
fallible_iterator::convert(ginger::parse_cookies(src))
.map(|cookie| Ok((cookie.name, cookie)))
.collect()
}
}
}
fn get_cookie_map_for_req<'a>(
req: &'a hyper::Request,
) -> Result, crate::Error> {
get_cookie_map_for_headers(req.headers())
}
fn get_cookie_map_for_headers(headers: &hyper::HeaderMap) -> Result {
get_cookie_map(get_cookies_string(headers)?).map_err(Into::into)
}
fn get_cookies_string(headers: &hyper::HeaderMap) -> Result, crate::Error> {
Ok(headers
.get(hyper::header::COOKIE)
.map(|x| x.to_str())
.transpose()?)
}
fn for_client(
mut new_req: hyper::Request,
src_headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
) -> Result, hyper::header::InvalidHeaderValue> {
let token = cookies.get("hitideToken").map(|c| c.value);
if let Some(token) = token {
new_req.headers_mut().insert(
hyper::header::AUTHORIZATION,
hyper::header::HeaderValue::from_str(&format!("Bearer {}", token))?,
);
}
if let Some(value) = src_headers.get(hyper::header::ACCEPT_LANGUAGE) {
new_req
.headers_mut()
.insert(hyper::header::ACCEPT_LANGUAGE, value.clone());
}
Ok(new_req)
}
async fn fetch_base_data(
backend_host: &str,
http_client: &crate::HttpClient,
headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
) -> Result {
let login = {
let api_res = http_client
.request(for_client(
hyper::Request::get(format!("{}/api/unstable/logins/~current", backend_host))
.body(Default::default())?,
headers,
&cookies,
)?)
.await?;
if api_res.status() == hyper::StatusCode::UNAUTHORIZED {
Ok(None)
} else {
let api_res = res_to_error(api_res).await?;
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
serde_json::from_slice(&api_res)
}
}?;
Ok(PageBaseData { login })
}
fn html_response(html: String) -> hyper::Response {
let mut res = hyper::Response::new(html.into());
res.headers_mut().insert(
hyper::header::CONTENT_TYPE,
hyper::header::HeaderValue::from_static("text/html"),
);
res
}
async fn page_about(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
use std::convert::TryInto;
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
.get(
format!("{}/api/unstable/instance", ctx.backend_host)
.try_into()
.unwrap(),
)
.await?,
)
.await?;
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: RespInstanceInfo = serde_json::from_slice(&api_res)?;
let title = lang.tr("about_title", None);
Ok(html_response(render::html! {
{title.as_ref()}
{
lang.tr(
"about_versions",
Some(&fluent::fluent_args![
"hitide_version" => env!("CARGO_PKG_VERSION"),
"backend_name" => api_res.software.name,
"backend_version" => api_res.software.version
])
)
}
{lang.tr("about_what_is", None)}
{lang.tr("about_text1", None)}
{" "}{"ActivityPub"} {"."}
{lang.tr("about_text2", None)}
{" "}
{lang.tr("about_sourcehut", None)} {"."}
}))
}
async fn page_comment(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let cookies = get_cookie_map_for_req(&req)?;
page_comment_inner(comment_id, req.headers(), &cookies, ctx, None, None).await
}
async fn page_comment_inner(
comment_id: i64,
headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
ctx: Arc,
display_error: Option,
prev_values: Option<&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/comments/{}{}",
ctx.backend_host,
comment_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 comment: RespCommentInfo<'_> = serde_json::from_slice(&api_res)?;
let title = lang.tr("comment", None);
Ok(html_response(render::html! {
{
if let Some(post) = &comment.post {
Some(render::rsx! {
{lang.tr("to_post", None)}{" "}{post.title.as_ref()}
})
} else {
None
}
}
{
if base_data.login.is_some() {
Some(render::rsx! {
<>
{
if comment.as_ref().your_vote.is_some() {
render::rsx! {
}
} else {
render::rsx! {
}
}
}
>
})
} else {
None
}
}
{
if let Some(parent) = &comment.parent {
Some(render::rsx! {
})
} else {
None
}
}
{":"}
{
if author_is_me(&comment.as_ref().author, &base_data.login) {
Some(render::rsx! {
{lang.tr("delete", None)}
})
} else {
None
}
}
{
display_error.map(|msg| {
render::rsx! {
{msg}
}
})
}
{
if base_data.login.is_some() {
Some(render::rsx! {
})
} else {
None
}
}
{
comment.as_ref().replies.as_ref().unwrap().iter().map(|reply| {
render::rsx! {
}
}).collect::>()
}
}))
}
async fn page_comment_delete(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let cookies = get_cookie_map_for_req(&req)?;
page_comment_delete_inner(comment_id, ctx, &req.headers(), &cookies, None).await
}
async fn page_comment_delete_inner(
comment_id: i64,
ctx: Arc,
headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
display_error: Option,
) -> 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 referer = headers
.get(hyper::header::REFERER)
.and_then(|x| x.to_str().ok());
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!(
"{}/api/unstable/comments/{}",
ctx.backend_host, comment_id
))
.body(Default::default())?,
headers,
&cookies,
)?)
.await?,
)
.await?;
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let comment: RespPostCommentInfo<'_> = serde_json::from_slice(&api_res)?;
let title = lang.tr("comment_delete_title", None);
Ok(html_response(render::html! {
{":"}
{lang.tr("comment_delete_question", None)}
{
display_error.map(|msg| {
render::rsx! {
{msg}
}
})
}
}))
}
async fn handler_comment_delete_confirm(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let (req_parts, body) = req.into_parts();
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let body = hyper::body::to_bytes(body).await?;
let body: ReturnToParams = serde_urlencoded::from_bytes(&body)?;
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::delete(format!(
"{}/api/unstable/comments/{}",
ctx.backend_host, comment_id,
))
.body("".into())?,
&req_parts.headers,
&cookies,
)?)
.await?,
)
.await;
match api_res {
Ok(_) => Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::LOCATION,
if let Some(return_to) = &body.return_to {
&return_to
} else {
"/"
},
)
.body("Successfully deleted.".into())?),
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
page_comment_delete_inner(comment_id, ctx, &req_parts.headers, &cookies, Some(message))
.await
}
Err(other) => Err(other),
}
}
async fn handler_comment_like(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let cookies = get_cookie_map_for_req(&req)?;
let referer = req
.headers()
.get(hyper::header::REFERER)
.and_then(|x| x.to_str().ok());
res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::put(format!(
"{}/api/unstable/comments/{}/your_vote",
ctx.backend_host, comment_id
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.await?;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::LOCATION,
(if let Some(referer) = referer {
Cow::Borrowed(referer)
} else {
format!("/comments/{}", comment_id).into()
})
.as_ref(),
)
.body("Successfully liked.".into())?)
}
async fn handler_comment_unlike(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let cookies = get_cookie_map_for_req(&req)?;
let referer = req
.headers()
.get(hyper::header::REFERER)
.and_then(|x| x.to_str().ok());
res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::delete(format!(
"{}/api/unstable/comments/{}/your_vote",
ctx.backend_host, comment_id
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.await?;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::LOCATION,
(if let Some(referer) = referer {
Cow::Borrowed(referer)
} else {
format!("/comments/{}", comment_id).into()
})
.as_ref(),
)
.body("Successfully unliked.".into())?)
}
async fn handler_comment_submit_reply(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (comment_id,) = params;
let (req_parts, body) = req.into_parts();
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let body = hyper::body::to_bytes(body).await?;
let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::post(format!(
"{}/api/unstable/comments/{}/replies",
ctx.backend_host, comment_id
))
.body(serde_json::to_vec(&body)?.into())?,
&req_parts.headers,
&cookies,
)?)
.await?,
)
.await;
match api_res {
Ok(_) => Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(hyper::header::LOCATION, format!("/comments/{}", comment_id))
.body("Successfully posted.".into())?),
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
page_comment_inner(
comment_id,
&req_parts.headers,
&cookies,
ctx,
Some(message),
Some(&body),
)
.await
}
Err(other) => Err(other),
}
}
async fn page_login(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
page_login_inner(ctx, req.into_parts().0, None, None).await
}
async fn page_login_inner(
ctx: Arc,
req_parts: http::request::Parts,
display_error: Option,
prev_values: Option<&serde_json::Value>,
) -> Result, crate::Error> {
let lang = crate::get_lang_for_headers(&req_parts.headers);
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let base_data = fetch_base_data(
&ctx.backend_host,
&ctx.http_client,
&req_parts.headers,
&cookies,
)
.await?;
let title = lang.tr("login", None);
Ok(html_response(render::html! {
{
display_error.map(|msg| {
render::rsx! {
{msg}
}
})
}
{lang.tr("or_start", None)}{" "}{lang.tr("login_signup_link", None)}
}))
}
pub async fn res_to_error(
res: hyper::Response,
) -> Result, crate::Error> {
let status = res.status();
if status.is_success() {
Ok(res)
} else {
let bytes = hyper::body::to_bytes(res.into_body()).await?;
Err(crate::Error::RemoteError((
status,
String::from_utf8_lossy(&bytes).into_owned(),
)))
}
}
async fn handler_login_submit(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
#[derive(Deserialize)]
struct LoginsCreateResponse<'a> {
token: &'a str,
}
let (req_parts, body) = req.into_parts();
let body = hyper::body::to_bytes(body).await?;
let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
let api_res = res_to_error(
ctx.http_client
.request(
hyper::Request::post(format!("{}/api/unstable/logins", ctx.backend_host))
.body(serde_json::to_vec(&body)?.into())?,
)
.await?,
)
.await;
match api_res {
Ok(api_res) => {
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: LoginsCreateResponse = serde_json::from_slice(&api_res)?;
let token = api_res.token;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::SET_COOKIE,
format!("hitideToken={}; Path=/; Max-Age={}", token, COOKIE_AGE),
)
.header(hyper::header::LOCATION, "/")
.body("Successfully logged in.".into())?)
}
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
page_login_inner(ctx, req_parts, Some(message), Some(&body)).await
}
Err(other) => Err(other),
}
}
async fn page_lookup(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
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?;
#[derive(Deserialize)]
struct LookupQuery<'a> {
query: Option>,
}
let query: LookupQuery<'_> = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;
let query = query.query;
#[derive(Deserialize)]
#[serde(rename_all = "snake_case")]
enum ActorType {
Community,
User,
#[serde(other)]
Unknown,
}
#[derive(Deserialize)]
struct LookupResult {
id: i64,
#[serde(rename = "type")]
kind: ActorType,
}
let api_res: Option, String>> = if let Some(query) = &query {
let api_res = res_to_error(
ctx.http_client
.request(
hyper::Request::get(format!(
"{}/api/unstable/actors:lookup/{}",
ctx.backend_host,
urlencoding::encode(&query)
))
.body(Default::default())?,
)
.await?,
)
.await;
Some(match api_res {
Ok(api_res) => {
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
Ok(serde_json::from_slice(&api_res)?)
}
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
Err(message)
}
Err(other) => return Err(other),
})
} else {
None
};
match api_res {
Some(Ok(items)) if !items.is_empty() => {
let item = &items[0];
let dest = match item.kind {
ActorType::Community => format!("/communities/{}", item.id),
ActorType::User => format!("/users/{}", item.id),
ActorType::Unknown => {
return Err(crate::Error::InternalStr(
"Unknown actor type received from lookup".to_owned(),
));
}
};
Ok(hyper::Response::builder()
.status(hyper::StatusCode::FOUND)
.header(hyper::header::LOCATION, dest)
.body("Redirecting…".into())?)
}
api_res => {
let title = lang.tr("lookup_title", None);
Ok(html_response(render::html! {
{title.as_ref()}
{
match api_res {
None => None,
Some(Ok(_)) => {
// non-empty case is handled above
Some(render::rsx! { {lang.tr("lookup_nothing", None)}
})
},
Some(Err(display_error)) => {
Some(render::rsx! {
{display_error.into()}
})
}
}
}
}))
}
}
}
async fn page_new_community(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let cookies = get_cookie_map_for_req(&req)?;
page_new_community_inner(ctx, req.headers(), &cookies, None, None).await
}
async fn page_new_community_inner(
ctx: Arc,
headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
display_error: Option,
prev_values: Option<&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 title = lang.tr("community_create", None);
Ok(html_response(render::html! {
{title.as_ref()}
{
display_error.map(|msg| {
render::rsx! {
{msg}
}
})
}
}))
}
async fn handler_new_community_submit(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (req_parts, body) = req.into_parts();
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let body = hyper::body::to_bytes(body).await?;
let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
#[derive(Deserialize)]
struct CommunitiesCreateResponseCommunity {
id: i64,
}
#[derive(Deserialize)]
struct CommunitiesCreateResponse {
community: CommunitiesCreateResponseCommunity,
}
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::post(format!("{}/api/unstable/communities", ctx.backend_host))
.body(serde_json::to_vec(&body)?.into())?,
&req_parts.headers,
&cookies,
)?)
.await?,
)
.await;
match api_res {
Ok(api_res) => {
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: CommunitiesCreateResponse = serde_json::from_slice(&api_res)?;
let community_id = api_res.community.id;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::LOCATION,
format!("/communities/{}", community_id),
)
.body("Successfully created.".into())?)
}
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
page_new_community_inner(
ctx,
&req_parts.headers,
&cookies,
Some(message),
Some(&body),
)
.await
}
Err(other) => Err(other),
}
}
async fn page_notifications(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
use futures_util::future::TryFutureExt;
let lang = crate::get_lang_for_req(&req);
let cookies = get_cookie_map_for_req(&req)?;
let api_res: Result, _>, _> = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!(
"{}/api/unstable/users/~me/notifications",
ctx.backend_host
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.map_err(crate::Error::from)
.and_then(|body| hyper::body::to_bytes(body).map_err(crate::Error::from))
.await
.map(|body| serde_json::from_slice(&body));
let base_data =
fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?;
let title = lang.tr("notifications", None);
match api_res {
Err(crate::Error::RemoteError((_, message))) => {
let mut res = html_response(render::html! {
{title.as_ref()}
{message}
});
*res.status_mut() = hyper::StatusCode::FORBIDDEN;
Ok(res)
}
Err(other) => Err(other),
Ok(api_res) => {
let notifications = api_res?;
Ok(html_response(render::html! {
{title.as_ref()}
{
if notifications.is_empty() {
Some(render::rsx! { {lang.tr("nothing", None)}
})
} else {
None
}
}
{
notifications.iter()
.map(|item| render::rsx! { })
.collect::>()
}
}))
}
}
}
async fn page_signup(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
page_signup_inner(ctx, req.headers(), None, None).await
}
async fn page_signup_inner(
ctx: Arc,
headers: &hyper::HeaderMap,
display_error: Option,
prev_values: Option<&serde_json::Value>,
) -> Result, crate::Error> {
let lang = crate::get_lang_for_headers(&headers);
let cookies = get_cookie_map_for_headers(&headers)?;
let base_data = fetch_base_data(&ctx.backend_host, &ctx.http_client, headers, &cookies).await?;
let title = lang.tr("register", None);
Ok(html_response(render::html! {
{
display_error.map(|msg| {
render::rsx! {
{msg}
}
})
}
}))
}
async fn handler_signup_submit(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
#[derive(Deserialize)]
struct UsersCreateResponse<'a> {
token: &'a str,
}
let (req_parts, body) = req.into_parts();
let body = hyper::body::to_bytes(body).await?;
let mut body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
body["login"] = true.into();
let api_res = res_to_error(
ctx.http_client
.request(
hyper::Request::post(format!("{}/api/unstable/users", ctx.backend_host))
.body(serde_json::to_vec(&body)?.into())?,
)
.await?,
)
.await;
match api_res {
Ok(api_res) => {
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: UsersCreateResponse = serde_json::from_slice(&api_res)?;
let token = api_res.token;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(
hyper::header::SET_COOKIE,
format!("hitideToken={}; Path=/; Max-Age={}", token, COOKIE_AGE),
)
.header(hyper::header::LOCATION, "/")
.body("Successfully registered new account.".into())?)
}
Err(crate::Error::RemoteError((status, message))) if status.is_client_error() => {
page_signup_inner(ctx, &req_parts.headers, Some(message), Some(&body)).await
}
Err(other) => Err(other),
}
}
async fn page_user(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (user_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 user = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!(
"{}/api/unstable/users/{}{}",
ctx.backend_host,
user_id,
if base_data.login.is_some() {
"?include_your=true"
} else {
""
},
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.await?;
let user = hyper::body::to_bytes(user.into_body()).await?;
let user: RespUserInfo<'_> = serde_json::from_slice(&user)?;
let things = res_to_error(
ctx.http_client
.request(
hyper::Request::get(format!(
"{}/api/unstable/users/{}/things",
ctx.backend_host, user_id,
))
.body(Default::default())?,
)
.await?,
)
.await?;
let things = hyper::body::to_bytes(things.into_body()).await?;
let things: Vec = serde_json::from_slice(&things)?;
let title = user.as_ref().username.as_ref();
Ok(html_response(render::html! {
{title}
{format!("@{}@{}", user.as_ref().username, user.as_ref().host)}
{
if user.as_ref().local {
None
} else if let Some(remote_url) = &user.as_ref().remote_url {
Some(render::rsx! {
})
} else {
None // shouldn't ever happen
}
}
{
if let Some(your_note) = &user.your_note {
Some(render::rsx! {
})
} else {
None
}
}
{
if user.your_note.is_none() && base_data.login.is_some() {
Some(render::rsx! {
})
} else {
None
}
}
{
if let Some(login) = &base_data.login {
if login.user.id == user_id {
Some(render::rsx! { {lang.tr("edit", None)} })
} else {
None
}
} else {
None
}
}
{user.description.as_ref()}
{
if things.is_empty() {
Some(render::rsx! { {lang.tr("nothing", None)}
})
} else {
None
}
}
{
things.iter().map(|thing| {
ThingItem { thing, lang: &lang }
})
.collect::>()
}
}))
}
async fn page_user_edit(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (user_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 title = lang.tr("user_edit_title", None);
let is_me = match &base_data.login {
None => false,
Some(login) => login.user.id == user_id,
};
if !is_me {
let mut res = html_response(render::html! {
{title.as_ref()}
{lang.tr("user_edit_not_you", None)}
});
*res.status_mut() = hyper::StatusCode::FORBIDDEN;
return Ok(res);
}
let user = res_to_error(
ctx.http_client
.request(
hyper::Request::get(format!(
"{}/api/unstable/users/{}",
ctx.backend_host, user_id
))
.body(Default::default())?,
)
.await?,
)
.await?;
let user = hyper::body::to_bytes(user.into_body()).await?;
let user: RespUserInfo<'_> = serde_json::from_slice(&user)?;
Ok(html_response(render::html! {
{title.as_ref()}
}))
}
async fn handler_user_edit_submit(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (user_id,) = params;
let (req_parts, body) = req.into_parts();
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let body = hyper::body::to_bytes(body).await?;
let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::patch(format!("{}/api/unstable/users/~me", ctx.backend_host))
.body(serde_json::to_vec(&body)?.into())?,
&req_parts.headers,
&cookies,
)?)
.await?,
)
.await?;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(hyper::header::LOCATION, format!("/users/{}", user_id))
.body("Successfully created.".into())?)
}
async fn page_user_your_note_edit(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (user_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 user = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!(
"{}/api/unstable/users/{}?include_your=true",
ctx.backend_host, user_id,
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.await?;
let user = hyper::body::to_bytes(user.into_body()).await?;
let user: RespUserInfo<'_> = serde_json::from_slice(&user)?;
let title = lang.tr("your_note_edit", None);
Ok(html_response(render::html! {
{title.as_ref()}
{user.your_note.map(|x| x.content_text)}
{lang.tr("save", None)}
}))
}
async fn handler_user_your_note_edit_submit(
params: (i64,),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let (user_id,) = params;
let (req_parts, body) = req.into_parts();
let cookies = get_cookie_map_for_headers(&req_parts.headers)?;
let body = hyper::body::to_bytes(body).await?;
let body: serde_json::Value = serde_urlencoded::from_bytes(&body)?;
res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::put(format!(
"{}/api/unstable/users/{}/your_note",
ctx.backend_host, user_id
))
.body(serde_json::to_vec(&body)?.into())?,
&req_parts.headers,
&cookies,
)?)
.await?,
)
.await?;
Ok(hyper::Response::builder()
.status(hyper::StatusCode::SEE_OTHER)
.header(hyper::header::LOCATION, format!("/users/{}", user_id))
.body("Successfully created.".into())?)
}
async fn page_home(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
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?;
if base_data.login.is_none() {
return page_all_inner(req.headers(), &cookies, &base_data, ctx).await;
}
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!(
"{}/api/unstable/users/~me/following:posts",
ctx.backend_host
))
.body(Default::default())?,
req.headers(),
&cookies,
)?)
.await?,
)
.await?;
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: Vec> = serde_json::from_slice(&api_res)?;
Ok(html_response(render::html! {
{
if api_res.is_empty() {
Some(render::rsx! {
{lang.tr("nothing", None)}
{" "}
{lang.tr("home_follow_prompt1", None)}
{" "}
{lang.tr("home_follow_prompt2", None)}
})
} else {
None
}
}
{api_res.iter().map(|post| {
PostItem { post, in_community: false, no_user: false, lang: &lang }
}).collect::>()}
}))
}
async fn page_all(
_: (),
ctx: Arc,
req: hyper::Request,
) -> Result, crate::Error> {
let cookies = get_cookie_map_for_req(&req)?;
let base_data =
fetch_base_data(&ctx.backend_host, &ctx.http_client, req.headers(), &cookies).await?;
page_all_inner(req.headers(), &cookies, &base_data, ctx).await
}
async fn page_all_inner(
headers: &hyper::header::HeaderMap,
cookies: &CookieMap<'_>,
base_data: &crate::PageBaseData,
ctx: Arc,
) -> Result, crate::Error> {
let lang = crate::get_lang_for_headers(headers);
let api_res = res_to_error(
ctx.http_client
.request(for_client(
hyper::Request::get(format!("{}/api/unstable/posts", ctx.backend_host))
.body(Default::default())?,
headers,
&cookies,
)?)
.await?,
)
.await?;
let api_res = hyper::body::to_bytes(api_res.into_body()).await?;
let api_res: Vec> = serde_json::from_slice(&api_res)?;
Ok(html_response(render::html! {
{lang.tr("all_title", None)}
{
if api_res.is_empty() {
Some(render::rsx! {
{lang.tr("nothing_yet", None)}
})
} else {
None
}
}
{api_res.iter().map(|post| {
PostItem { post, in_community: false, no_user: false, lang: &lang }
}).collect::>()}
}))
}
pub fn route_root() -> crate::RouteNode<()> {
crate::RouteNode::new()
.with_handler_async("GET", page_home)
.with_child(
"about",
crate::RouteNode::new().with_handler_async("GET", page_about),
)
.with_child(
"all",
crate::RouteNode::new().with_handler_async("GET", page_all),
)
.with_child(
"comments",
crate::RouteNode::new().with_child_parse::(
crate::RouteNode::new()
.with_handler_async("GET", page_comment)
.with_child(
"delete",
crate::RouteNode::new()
.with_handler_async("GET", page_comment_delete)
.with_child(
"confirm",
crate::RouteNode::new()
.with_handler_async("POST", handler_comment_delete_confirm),
),
)
.with_child(
"like",
crate::RouteNode::new().with_handler_async("POST", handler_comment_like),
)
.with_child(
"unlike",
crate::RouteNode::new().with_handler_async("POST", handler_comment_unlike),
)
.with_child(
"submit_reply",
crate::RouteNode::new()
.with_handler_async("POST", handler_comment_submit_reply),
),
),
)
.with_child("communities", communities::route_communities())
.with_child(
"login",
crate::RouteNode::new()
.with_handler_async("GET", page_login)
.with_child(
"submit",
crate::RouteNode::new().with_handler_async("POST", handler_login_submit),
),
)
.with_child(
"lookup",
crate::RouteNode::new().with_handler_async("GET", page_lookup),
)
.with_child(
"new_community",
crate::RouteNode::new()
.with_handler_async("GET", page_new_community)
.with_child(
"submit",
crate::RouteNode::new()
.with_handler_async("POST", handler_new_community_submit),
),
)
.with_child(
"notifications",
crate::RouteNode::new().with_handler_async("GET", page_notifications),
)
.with_child("posts", posts::route_posts())
.with_child(
"signup",
crate::RouteNode::new()
.with_handler_async("GET", page_signup)
.with_child(
"submit",
crate::RouteNode::new().with_handler_async("POST", handler_signup_submit),
),
)
.with_child("static", r#static::route_static())
.with_child(
"users",
crate::RouteNode::new().with_child_parse::(
crate::RouteNode::new()
.with_handler_async("GET", page_user)
.with_child(
"edit",
crate::RouteNode::new()
.with_handler_async("GET", page_user_edit)
.with_child(
"submit",
crate::RouteNode::new()
.with_handler_async("POST", handler_user_edit_submit),
),
)
.with_child(
"your_note/edit",
crate::RouteNode::new()
.with_handler_async("GET", page_user_your_note_edit)
.with_child(
"submit",
crate::RouteNode::new()
.with_handler_async("POST", handler_user_your_note_edit_submit),
),
),
),
)
}