use super::{
JustURL, MaybeIncludeYour, RespAvatarInfo, RespMinimalAuthorInfo, RespMinimalCommentInfo,
RespMinimalCommunityInfo, RespPostCommentInfo, RespPostListPost,
};
use crate::{CommentLocalID, CommunityLocalID, PostLocalID, UserLocalID};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::HashSet;
use std::sync::Arc;
async fn get_post_comments<'a>(
post_id: PostLocalID,
include_your_for: Option<UserLocalID>,
db: &tokio_postgres::Client,
ctx: &'a crate::BaseContext,
) -> Result<Vec<RespPostCommentInfo<'a>>, crate::Error> {
use futures::TryStreamExt;
let sql1 = "SELECT reply.id, reply.author, reply.content_text, reply.created, reply.content_html, person.username, person.local, person.ap_id, reply.deleted, person.avatar, attachment_href, reply.local";
let (sql2, values): (_, Vec<&(dyn tokio_postgres::types::ToSql + Sync)>) =
if include_your_for.is_some() {
(
", EXISTS(SELECT 1 FROM reply_like WHERE reply = reply.id AND person = $2)",
vec![&post_id, &include_your_for],
)
} else {
("", vec![&post_id])
};
let sql3 = " FROM reply LEFT OUTER JOIN person ON (person.id = reply.author) WHERE post=$1 AND parent IS NULL ORDER BY hot_rank((SELECT COUNT(*) FROM reply_like WHERE reply = reply.id AND person != reply.author), reply.created) DESC";
let sql: &str = &format!("{}{}{}", sql1, sql2, sql3);
let stream = crate::query_stream(db, sql, &values[..]).await?;
let mut comments: Vec<_> = stream
.map_err(crate::Error::from)
.and_then(|row| {
let id = CommentLocalID(row.get(0));
let content_text: Option<String> = row.get(2);
let content_html: Option<String> = row.get(4);
let created: chrono::DateTime<chrono::FixedOffset> = row.get(3);
let author_username: Option<String> = row.get(5);
let author = author_username.map(|author_username| {
let author_id = UserLocalID(row.get(1));
let author_local: bool = row.get(6);
let author_ap_id: Option<&str> = row.get(7);
let author_avatar: Option<&str> = row.get(9);
RespMinimalAuthorInfo {
id: author_id,
username: author_username.into(),
local: author_local,
host: crate::get_actor_host_or_unknown(
author_local,
author_ap_id,
&ctx.local_hostname,
),
remote_url: author_ap_id.map(|x| x.to_owned().into()),
avatar: author_avatar.map(|url| RespAvatarInfo {
url: url.to_owned().into(),
}),
}
});
futures::future::ok((
(),
RespPostCommentInfo {
base: RespMinimalCommentInfo {
id,
content_text: content_text.map(From::from),
content_html_safe: content_html.map(|html| ammonia::clean(&html)),
},
attachments: match ctx
.process_attachments_inner(row.get::<_, Option<_>>(10).map(Cow::Owned), id)
{
None => vec![],
Some(href) => vec![JustURL { url: href }],
},
author,
created: created.to_rfc3339(),
deleted: row.get(8),
local: row.get(11),
replies: None,
has_replies: false,
your_vote: match include_your_for {
None => None,
Some(_) => Some(if row.get(12) {
Some(crate::Empty {})
} else {
None
}),
},
},
))
})
.try_collect()
.await?;
super::apply_comments_replies(&mut comments, include_your_for, 2, db, &ctx).await?;
Ok(comments.into_iter().map(|(_, comment)| comment).collect())
}
async fn route_unstable_posts_list(
_: (),
ctx: Arc<crate::RouteContext>,
req: hyper::Request<hyper::Body>,
) -> Result<hyper::Response<hyper::Body>, crate::Error> {
let query: MaybeIncludeYour = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;
let db = ctx.db_pool.get().await?;
let include_your_for