diff options
author | Colin Reeder <vpzomtrrfrt@gmail.com> | 2020-12-04 17:31:02 -0700 |
---|---|---|
committer | Colin Reeder <vpzomtrrfrt@gmail.com> | 2020-12-04 17:31:02 -0700 |
commit | 98cc8b10291d36a5c81f1b72e0a557112f1c1647 (patch) | |
tree | 4cc379a248de35083993e39acf082f14b61e275e | |
parent | 3419324fd722e1edaae572a396816fd63d6bcf82 (diff) | |
parent | 76370ee067692be9a6c2a975a83924c3b50052c1 (diff) |
Merge branch 'post-search' into post-search-integration
-rw-r--r-- | migrations/20201026222917_post-fts/down.sql | 3 | ||||
-rw-r--r-- | migrations/20201026222917_post-fts/up.sql | 3 | ||||
-rw-r--r-- | openapi/openapi.json | 17 | ||||
-rw-r--r-- | res/lang/en.ftl | 1 | ||||
-rw-r--r-- | src/routes/api/posts.rs | 73 |
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?; |