summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorColin Reeder <vpzomtrrfrt@gmail.com>2020-12-04 17:31:02 -0700
committerColin Reeder <vpzomtrrfrt@gmail.com>2020-12-04 17:31:02 -0700
commit98cc8b10291d36a5c81f1b72e0a557112f1c1647 (patch)
tree4cc379a248de35083993e39acf082f14b61e275e
parent3419324fd722e1edaae572a396816fd63d6bcf82 (diff)
parent76370ee067692be9a6c2a975a83924c3b50052c1 (diff)
Merge branch 'post-search' into post-search-integration
-rw-r--r--migrations/20201026222917_post-fts/down.sql3
-rw-r--r--migrations/20201026222917_post-fts/up.sql3
-rw-r--r--openapi/openapi.json17
-rw-r--r--res/lang/en.ftl1
-rw-r--r--src/routes/api/posts.rs73
5 files changed, 86 insertions, 11 deletions
diff --git a/migrations/20201026222917_post-fts/down.sql b/migrations/20201026222917_post-fts/down.sql
new file mode 100644
index 0000000..85568d8
--- /dev/null
+++ b/migrations/20201026222917_post-fts/down.sql
@@ -0,0 +1,3 @@
+BEGIN;
+ DROP INDEX post_fts;
+COMMIT;
diff --git a/migrations/20201026222917_post-fts/up.sql b/migrations/20201026222917_post-fts/up.sql
new file mode 100644
index 0000000..79f8fd6
--- /dev/null
+++ b/migrations/20201026222917_post-fts/up.sql
@@ -0,0 +1,3 @@
+BEGIN;
+ CREATE INDEX post_fts ON post USING gin(to_tsvector('english', title || ' ' || COALESCE(content_text, content_markdown, content_html, '')));
+COMMIT;
diff --git a/openapi/openapi.json b/openapi/openapi.json
index 2d9586b..12c698c 100644
--- a/openapi/openapi.json
+++ b/openapi/openapi.json
@@ -1078,6 +1078,23 @@
"in": "query",
"required": false,
"schema": {"type": "boolean"}
+ },
+ {
+ "name": "sort",
+ "in": "query",
+ "required": false,
+ "schema": {
+ "oneOf": [
+ {"$ref": "#/components/schemas/SortType"},
+ {"type": "string", "enum": ["relevant"]}
+ ]
+ }
+ },
+ {
+ "name": "search",
+ "in": "query",
+ "required": false,
+ "schema": {"type": "string"}
}
],
"responses": {
diff --git a/res/lang/en.ftl b/res/lang/en.ftl
index 4c17d07..fbacec9 100644
--- a/res/lang/en.ftl
+++ b/res/lang/en.ftl
@@ -28,6 +28,7 @@ post_needs_content = Post must contain one of href, content_text, or content_mar
post_not_in_community = That post is not in this community
post_not_yours = That's not your post
root = lotide is running. Note that lotide itself does not include a frontend, and you'll need to install one separately.
+sort_relevant_not_search = Sorting by relevance is only allowed when searching
user_email_invalid = Specified email address is invalid
user_name_disallowed_chars = Username contains disallowed characters
user_no_avatar = That user does not have an avatar
diff --git a/src/routes/api/posts.rs b/src/routes/api/posts.rs
index 2bfa0e2..9489db9 100644
--- a/src/routes/api/posts.rs
+++ b/src/routes/api/posts.rs
@@ -6,6 +6,7 @@ use crate::{CommentLocalID, CommunityLocalID, PostLocalID, UserLocalID};
use serde_derive::{Deserialize, Serialize};
use std::borrow::Cow;
use std::collections::HashSet;
+use std::fmt::Write;
use std::sync::Arc;
async fn get_post_comments<'a>(
@@ -110,8 +111,40 @@ 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(""))?;
+ #[derive(Deserialize)]
+ #[serde(rename_all = "snake_case")]
+ enum PostsListExtraSortType {
+ Relevant,
+ }
+
+ #[derive(Deserialize)]
+ #[serde(untagged)]
+ enum PostsListSortType {
+ Normal(super::SortType),
+ Extra(PostsListExtraSortType),
+ }
+
+ impl Default for PostsListSortType {
+ fn default() -> Self {
+ Self::Normal(super::SortType::Hot)
+ }
+ }
+
+ #[derive(Deserialize)]
+ struct PostsListQuery<'a> {
+ #[serde(default)]
+ search: Option<Cow<'a, str>>,
+
+ #[serde(default)]
+ include_your: bool,
+
+ #[serde(default)]
+ sort: PostsListSortType,
+ }
+ let query: PostsListQuery = serde_urlencoded::from_str(req.uri().query().unwrap_or(""))?;
+
+ let lang = crate::get_lang_for_req(&req);
let db = ctx.db_pool.get().await?;
let include_your_for = if query.include_your {
@@ -121,22 +154,40 @@ async fn route_unstable_posts_list(
None
};
+ let mut search_value_idx = None;
+
let limit: i64 = 30;
+ let mut sql = String::from("SELECT post.id, post.author, post.href, post.content_text, post.title, post.created, post.content_html, community.id, community.name, community.local, community.ap_id, person.username, person.local, person.ap_id, person.avatar, (SELECT COUNT(*) FROM post_like WHERE post_like.post = post.id)");
let mut values: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = vec![&limit];
- let include_your_idx = match &include_your_for {
- None => None,
- Some(user) => {
- values.push(user);
- Some(values.len())
+ if let Some(user) = &include_your_for {
+ values.push(user);
+ sql.push_str(", EXISTS(SELECT 1 FROM post_like WHERE post=post.id AND person=$2)");
+ }
+ sql.push_str( " FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND deleted=FALSE");
+ if let Some(search) = &query.search {
+ values.push(search);
+ search_value_idx = Some(values.len());
+ write!(sql, " AND to_tsvector('english', title || ' ' || COALESCE(content_text, content_markdown, content_html, '')) @@ plainto_tsquery('english', ${})", values.len()).unwrap();
+ }
+ sql.push_str(" ORDER BY ");
+ match query.sort {
+ PostsListSortType::Normal(ty) => sql.push_str(ty.post_sort_sql()),
+ PostsListSortType::Extra(PostsListExtraSortType::Relevant) => {
+ if let Some(search_value_idx) = search_value_idx {
+ write!(sql, "ts_rank_cd(to_tsvector('english', title || ' ' || COALESCE(content_text, content_markdown, content_html, '')), plainto_tsquery('english', ${}))", search_value_idx).unwrap();
+ } else {
+ return Err(crate::Error::UserError(crate::simple_response(
+ hyper::StatusCode::BAD_REQUEST,
+ lang.tr("sort_relevant_not_search", None).into_owned(),
+ )));
+ }
}
- };
+ }
+ sql.push_str(" LIMIT $1");
- let sql: &str = &format!(
- "SELECT {} FROM community, post LEFT OUTER JOIN person ON (person.id = post.author) WHERE post.community = community.id AND deleted=FALSE ORDER BY hot_rank((SELECT COUNT(*) FROM post_like WHERE post = post.id AND person != post.author), post.created) DESC LIMIT $1",
- super::common_posts_list_query(include_your_idx),
- );
+ let sql: &str = &sql;
let stream = crate::query_stream(&db, sql, &values).await?;