diff --git a/lib/philomena/comments/elasticsearch.ex b/lib/philomena/comments/elasticsearch.ex index 0d05869d..a65cf899 100644 --- a/lib/philomena/comments/elasticsearch.ex +++ b/lib/philomena/comments/elasticsearch.ex @@ -40,7 +40,7 @@ defmodule Philomena.Comments.Elasticsearch do author: if(!!comment.user and !comment.anonymous, do: comment.user.name), image_tag_ids: comment.image.tags |> Enum.map(& &1.id), anonymous: comment.anonymous, - hidden_from_users: comment.hidden_from_users, + hidden_from_users: comment.image.hidden_from_users || comment.hidden_from_users, body: comment.body } end diff --git a/lib/philomena/posts/elasticsearch.ex b/lib/philomena/posts/elasticsearch.ex new file mode 100644 index 00000000..ebc0778e --- /dev/null +++ b/lib/philomena/posts/elasticsearch.ex @@ -0,0 +1,62 @@ +defmodule Philomena.Posts.Elasticsearch do + def mapping do + %{ + settings: %{ + index: %{ + number_of_shards: 5, + max_result_window: 10_000_000 + } + }, + mappings: %{ + post: %{ + _all: %{enabled: false}, + dynamic: false, + properties: %{ + id: %{type: "integer"}, + body: %{type: "text", analyzer: "snowball"}, + ip: %{type: "ip"}, + user_agent: %{type: "keyword"}, + referrer: %{type: "keyword"}, + fingerprint: %{type: "keyword"}, + subject: %{type: "text", analyzer: "snowball"}, + author: %{type: "keyword"}, + topic_position: %{type: "integer"}, + forum_id: %{type: "keyword"}, + topic_id: %{type: "keyword"}, + user_id: %{type: "keyword"}, + anonymous: %{type: "boolean"}, + updated_at: %{type: "date"}, + created_at: %{type: "date"}, + deleted: %{type: "boolean"}, + access_level: %{type: "keyword"}, + destroyed_content: %{type: "boolean"} + } + } + } + } + end + + # [:user, topic: :forum] + def as_json(post) do + %{ + id: post.id, + topic_id: post.topic_id, + body: post.body, + author: if(!!post.user and !post.anonymous, do: String.downcase(post.user.name)), + subject: post.topic.title, + ip: post.ip |> to_string(), + user_agent: post.user_agent, + referrer: post.referrer, + fingerprint: post.fingerprint, + topic_position: post.topic_position, + forum_id: post.topic.forum_id, + user_id: post.user_id, + anonymous: post.anonymous, + created_at: post.created_at, + updated_at: post.updated_at, + deleted: post.hidden_from_users, + access_level: post.topic.forum.access_level, + destroyed_content: post.destroyed_content + } + end +end \ No newline at end of file diff --git a/lib/philomena/posts/post.ex b/lib/philomena/posts/post.ex index 953f486d..9364ef6d 100644 --- a/lib/philomena/posts/post.ex +++ b/lib/philomena/posts/post.ex @@ -2,6 +2,11 @@ defmodule Philomena.Posts.Post do use Ecto.Schema import Ecto.Changeset + use Philomena.Elasticsearch, + definition: Philomena.Posts.Elasticsearch, + index_name: "posts", + doc_type: "post" + alias Philomena.Users.User alias Philomena.Topics.Topic diff --git a/lib/philomena_web/controllers/comment_controller.ex b/lib/philomena_web/controllers/comment_controller.ex index 25ecabde..524d4595 100644 --- a/lib/philomena_web/controllers/comment_controller.ex +++ b/lib/philomena_web/controllers/comment_controller.ex @@ -1,7 +1,8 @@ defmodule PhilomenaWeb.CommentController do use PhilomenaWeb, :controller - alias Philomena.{Comments.Comment, Textile.Renderer} + alias Philomena.{Images.Image, Comments.Comment, Textile.Renderer} + alias Philomena.Repo import Ecto.Query def index(conn, params) do @@ -10,7 +11,7 @@ defmodule PhilomenaWeb.CommentController do %{ query: %{ bool: %{ - must: parse_search(params) ++ [%{term: %{hidden_from_users: false}}] + must: parse_search(conn, params) ++ [%{term: %{hidden_from_users: false}}] } }, sort: parse_sort(params) @@ -29,12 +30,12 @@ defmodule PhilomenaWeb.CommentController do render(conn, "index.html", comments: comments) end - defp parse_search(%{"comment" => comment_params}) do + defp parse_search(conn, %{"comment" => comment_params}) do parse_author(comment_params) ++ - parse_image_id(comment_params) ++ + parse_image_id(conn, comment_params) ++ parse_body(comment_params) end - defp parse_search(_params), do: [%{match_all: %{}}] + defp parse_search(_conn, _params), do: [%{match_all: %{}}] defp parse_author(%{"author" => author}) when author not in [nil, ""] do case String.contains?(author, ["*", "?"]) do @@ -53,11 +54,12 @@ defmodule PhilomenaWeb.CommentController do end defp parse_author(_params), do: [] - defp parse_image_id(%{"image_id" => image_id}) when image_id not in [nil, ""] do - case Integer.parse(image_id) do - {image_id, _rest} -> - [%{term: %{image_id: image_id}}] - + defp parse_image_id(conn, %{"image_id" => image_id}) when image_id not in [nil, ""] do + with {image_id, _rest} <- Integer.parse(image_id), + true <- valid_image?(conn.assigns.current_user, image_id) + do + [%{term: %{image_id: image_id}}] + else _error -> [] end @@ -74,4 +76,13 @@ defmodule PhilomenaWeb.CommentController do defp parse_sort(_params) do %{posted_at: :desc} end + + defp valid_image?(user, image_id) do + image = + Image + |> where(id: ^image_id) + |> Repo.one() + + Canada.Can.can?(user, :show, image) + end end diff --git a/lib/philomena_web/controllers/post_controller.ex b/lib/philomena_web/controllers/post_controller.ex index 40abdf59..34a0d637 100644 --- a/lib/philomena_web/controllers/post_controller.ex +++ b/lib/philomena_web/controllers/post_controller.ex @@ -1,7 +1,105 @@ defmodule PhilomenaWeb.PostController do use PhilomenaWeb, :controller - def index(conn, _params) do - + alias Philomena.{Forums.Forum, Posts.Post, Textile.Renderer} + alias Philomena.Repo + import Ecto.Query + + def index(conn, params) do + user = conn.assigns.current_user + + posts = + Post.search_records( + %{ + query: %{ + bool: %{ + must: parse_search(conn, params) ++ [%{term: %{deleted: false}}] + } + }, + sort: parse_sort(params) + }, + conn.assigns.pagination, + Post |> preload([topic: :forum, user: [awards: :badge]]) + ) + + rendered = + posts.entries + |> Renderer.render_collection() + + posts = + %{posts | entries: Enum.zip(posts.entries, rendered)} + + forums = + Forum + |> order_by(asc: :name) + |> Repo.all() + |> Enum.filter(&Canada.Can.can?(user, :show, &1)) + |> Enum.map(&{&1.name, &1.id}) + + forums = [{"-", ""} | forums] + + render(conn, "index.html", posts: posts, forums: forums) + end + + defp parse_search(conn, %{"post" => post_params}) do + parse_author(post_params) ++ + parse_subject(post_params) ++ + parse_forum_id(conn, post_params) ++ + parse_body(post_params) + end + defp parse_search(_conn, _params), do: [%{match_all: %{}}] + + defp parse_author(%{"author" => author}) when author not in [nil, ""] do + case String.contains?(author, ["*", "?"]) do + true -> + [ + %{wildcard: %{author: String.downcase(author)}}, + %{term: %{anonymous: false}} + ] + + false -> + [ + %{term: %{author: String.downcase(author)}}, + %{term: %{anonymous: false}} + ] + end + end + defp parse_author(_params), do: [] + + defp parse_subject(%{"subject" => subject}) when subject not in [nil, ""] do + [%{match: %{subject: %{query: subject, operator: "and"}}}] + end + defp parse_subject(_params), do: [] + + defp parse_forum_id(conn, %{"forum_id" => forum_id}) when forum_id not in [nil, ""] do + with {forum_id, _rest} <- Integer.parse(forum_id), + true <- valid_forum?(conn.assigns.current_user, forum_id) + do + [%{term: %{forum_id: forum_id}}] + else + _error -> + [] + end + end + defp parse_forum_id(_conn, _params), do: [] + + defp parse_body(%{"body" => body}) when body not in [nil, ""], + do: [%{match: %{body: body}}] + defp parse_body(_params), do: [] + + defp parse_sort(%{"post" => %{"sf" => sf, "sd" => sd}}) when sf in ["created_at", "_score"] and sd in ["desc", "asc"] do + %{sf => sd} + end + defp parse_sort(_params) do + %{created_at: :desc} + end + + defp valid_forum?(user, forum_id) do + forum = + Forum + |> where(id: ^forum_id) + |> Repo.one() + + Canada.Can.can?(user, :show, forum) end end \ No newline at end of file diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 0faaf97b..2f5612aa 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -96,6 +96,7 @@ defmodule PhilomenaWeb.Router do resources "/filters", FilterController resources "/profiles", ProfileController, only: [:show] resources "/captchas", CaptchaController, only: [:create] + resources "/posts", PostController, only: [:index] get "/:id", ImageController, :show end diff --git a/lib/philomena_web/templates/post/index.html.slime b/lib/philomena_web/templates/post/index.html.slime new file mode 100644 index 00000000..a628852e --- /dev/null +++ b/lib/philomena_web/templates/post/index.html.slime @@ -0,0 +1,49 @@ +elixir: + route = fn p -> Routes.post_path(@conn, :index, p) end + pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @posts, route: route + +.column-layout + .column-layout__left + .block + .block__content + h3 Search Posts + + = form_for @conn, Routes.post_path(@conn, :index), [as: :post, method: "get", class: "hform"], fn f -> + .field = label f, :author, "Author" + .field = text_input f, :author, class: "input hform__text", placeholder: "Author (* is wildcard)" + + .field = label f, :forum_id, "Forum" + .field = select f, :forum_id, @forums, class: "input input--wide" + + .field = label f, :body, "Body" + .field = textarea f, :body, class: "input input--wide", placeholder: "Body" + + .field = label f, :sf, "Sort by" + .field + => select f, :sf, ["Creation Date": "created_at", "Relevance": "_score"], class: "input" + => select f, :sd, ["Descending": "desc", "Ascending": "asc"], class: "input" + + .field + = submit "Search", class: "button button--state-primary" + + .column-layout__main + .block + .block__header + = pagination + + .post-search-results + = for {post, body} <- @posts do + .post-entry-wrapper + h3 + => link post.topic.forum.name, to: Routes.forum_path(@conn, :show, post.topic.forum) + ' » + => link post.topic.title, to: Routes.forum_topic_path(@conn, :show, post.topic.forum, post.topic) + ' » + a href=(Routes.forum_topic_path(@conn, :show, post.topic.forum, post.topic, post_id: post.id) <> "#post_#{post.id}") + = if post.topic_position == 0 do + ' Topic Opener + - else + ' Post + = post.topic_position + 1 + + = render PhilomenaWeb.PostView, "_post.html", post: post, body: body, conn: @conn \ No newline at end of file