diff --git a/.iex.exs b/.iex.exs index 41b58720..ed172734 100644 --- a/.iex.exs +++ b/.iex.exs @@ -1,3 +1,3 @@ -alias Philomena.{Repo, Comments.Comment, Posts.Post, Images.Image, Tags.Tag, Users.User} +alias Philomena.{Repo, Comments.Comment, Galleries.Gallery, Posts.Post, Images.Image, Topics.Topic, Tags.Tag, Users.User} import Ecto.Query import Ecto.Changeset diff --git a/lib/philomena/galleries/elasticsearch.ex b/lib/philomena/galleries/elasticsearch.ex new file mode 100644 index 00000000..98f6b18d --- /dev/null +++ b/lib/philomena/galleries/elasticsearch.ex @@ -0,0 +1,46 @@ +defmodule Philomena.Galleries.Elasticsearch do + def mapping do + %{ + settings: %{ + index: %{ + number_of_shards: 5, + max_result_window: 10_000_000 + } + }, + mappings: %{ + gallery: %{ + _all: %{enabled: false}, + dynamic: false, + properties: %{ + id: %{type: "integer"}, # keyword + image_count: %{type: "integer"}, + watcher_count: %{type: "integer"}, + updated_at: %{type: "date"}, + created_at: %{type: "date"}, + title: %{type: "keyword"}, + creator: %{type: "keyword"}, # missing creator_id + image_ids: %{type: "keyword"}, + watcher_ids: %{type: "keyword"}, # ??? + description: %{type: "text", analyzer: "snowball"} + } + } + } + } + end + + # [:subscribers, :creator, :interactions] + def as_json(gallery) do + %{ + id: gallery.id, + image_count: gallery.image_count, + watcher_count: length(gallery.subscribers), + watcher_ids: Enum.map(gallery.subscribers, & &1.id), + updated_at: gallery.updated_at, + created_at: gallery.created_at, + title: String.downcase(gallery.title), + creator: String.downcase(gallery.creator.name), + image_ids: Enum.map(gallery.interactions, & &1.image_id), + description: gallery.description + } + end +end diff --git a/lib/philomena/galleries/gallery.ex b/lib/philomena/galleries/gallery.ex index eb25398f..bbe9390b 100644 --- a/lib/philomena/galleries/gallery.ex +++ b/lib/philomena/galleries/gallery.ex @@ -2,6 +2,11 @@ defmodule Philomena.Galleries.Gallery do use Ecto.Schema import Ecto.Changeset + use Philomena.Elasticsearch, + definition: Philomena.Galleries.Elasticsearch, + index_name: "galleries", + doc_type: "gallery" + alias Philomena.Images.Image alias Philomena.Users.User diff --git a/lib/philomena/images.ex b/lib/philomena/images.ex index 3d7de462..2b5031c4 100644 --- a/lib/philomena/images.ex +++ b/lib/philomena/images.ex @@ -141,7 +141,7 @@ defmodule Philomena.Images do {:error, %Ecto.Changeset{}} """ - def create_subscription(image, nil), do: {:ok, nil} + def create_subscription(_image, nil), do: {:ok, nil} def create_subscription(image, user) do %Subscription{image_id: image.id, user_id: user.id} |> Subscription.changeset(%{}) diff --git a/lib/philomena/topics.ex b/lib/philomena/topics.ex index a235c406..868a2e42 100644 --- a/lib/philomena/topics.ex +++ b/lib/philomena/topics.ex @@ -123,7 +123,7 @@ defmodule Philomena.Topics do {:error, %Ecto.Changeset{}} """ - def create_subscription(topic, nil), do: {:ok, nil} + def create_subscription(_topic, nil), do: {:ok, nil} def create_subscription(topic, user) do %Subscription{topic_id: topic.id, user_id: user.id} |> Subscription.changeset(%{}) diff --git a/lib/philomena_web/controllers/comment_controller.ex b/lib/philomena_web/controllers/comment_controller.ex index ab72ff87..8703645e 100644 --- a/lib/philomena_web/controllers/comment_controller.ex +++ b/lib/philomena_web/controllers/comment_controller.ex @@ -38,7 +38,7 @@ defmodule PhilomenaWeb.CommentController do end defp parse_search(_conn, _params), do: [%{match_all: %{}}] - defp parse_author(%{"author" => author}) when author not in [nil, ""] do + defp parse_author(%{"author" => author}) when is_binary(author) and author not in [nil, ""] do case String.contains?(author, ["*", "?"]) do true -> [ @@ -55,7 +55,7 @@ defmodule PhilomenaWeb.CommentController do end defp parse_author(_params), do: [] - defp parse_image_id(conn, %{"image_id" => image_id}) when image_id not in [nil, ""] do + defp parse_image_id(conn, %{"image_id" => image_id}) when is_binary(image_id) and image_id not in [nil, ""] do with {image_id, _rest} <- Integer.parse(image_id), true <- valid_image?(conn.assigns.current_user, image_id) do @@ -67,7 +67,7 @@ defmodule PhilomenaWeb.CommentController do end defp parse_image_id(_conn, _params), do: [] - defp parse_body(%{"body" => body}) when body not in [nil, ""], + defp parse_body(%{"body" => body}) when is_binary(body) and body not in [nil, ""], do: [%{match: %{body: body}}] defp parse_body(_params), do: [] diff --git a/lib/philomena_web/controllers/gallery_controller.ex b/lib/philomena_web/controllers/gallery_controller.ex new file mode 100644 index 00000000..ac07a0ed --- /dev/null +++ b/lib/philomena_web/controllers/gallery_controller.ex @@ -0,0 +1,69 @@ +defmodule PhilomenaWeb.GalleryController do + use PhilomenaWeb, :controller + + alias Philomena.Galleries.Gallery + import Ecto.Query + + plug :load_resource, model: Gallery, preload: [:thumbnail, :creator] + + def index(conn, params) do + galleries = + Gallery.search_records( + %{ + query: %{ + bool: %{ + must: parse_search(params) + } + }, + sort: parse_sort(params), + }, + conn.assigns.pagination, + Gallery |> preload([:thumbnail, :creator]) + ) + + render(conn, "index.html", galleries: galleries, layout_class: "layout--wide") + end + + def show(_conn, _params) do + end + + defp parse_search(%{"gallery" => gallery_params}) do + parse_title(gallery_params) ++ + parse_creator(gallery_params) ++ + parse_included_image(gallery_params) ++ + parse_description(gallery_params) + end + defp parse_search(_params), do: [%{match_all: %{}}] + + defp parse_title(%{"title" => title}) when is_binary(title) and title not in [nil, ""], + do: [%{wildcard: %{title: "*" <> String.downcase(title) <> "*"}}] + defp parse_title(_params), do: [] + + defp parse_creator(%{"creator" => creator}) when is_binary(creator) and creator not in [nil, ""], + do: [%{term: %{creator: String.downcase(creator)}}] + defp parse_creator(_params), do: [] + + defp parse_included_image(%{"include_image" => image_id}) when is_binary(image_id) and image_id not in [nil, ""] do + with {image_id, _rest} <- Integer.parse(image_id) do + [%{term: %{image_id: image_id}}] + else + _ -> + [] + end + end + defp parse_included_image(_params), do: [] + + defp parse_description(%{"description" => description}) when is_binary(description) and description not in [nil, ""], + do: [%{match: %{description: %{query: description, operator: :and}}}] + defp parse_description(_params), do: [] + + defp parse_sort(%{"gallery" => %{"sf" => sf, "sd" => sd}}) + when sf in ["created_at", "updated_at", "image_count", "_score"] + and sd in ["desc", "asc"] + do + %{sf => sd} + end + defp parse_sort(_params) do + %{created_at: :desc} + end +end \ No newline at end of file diff --git a/lib/philomena_web/controllers/post_controller.ex b/lib/philomena_web/controllers/post_controller.ex index b0915e11..b023e9cb 100644 --- a/lib/philomena_web/controllers/post_controller.ex +++ b/lib/philomena_web/controllers/post_controller.ex @@ -49,7 +49,7 @@ defmodule PhilomenaWeb.PostController do end defp parse_search(_conn, _params), do: [%{match_all: %{}}] - defp parse_author(%{"author" => author}) when author not in [nil, ""] do + defp parse_author(%{"author" => author}) when is_binary(author) and author not in [nil, ""] do case String.contains?(author, ["*", "?"]) do true -> [ @@ -66,12 +66,12 @@ defmodule PhilomenaWeb.PostController do end defp parse_author(_params), do: [] - defp parse_subject(%{"subject" => subject}) when subject not in [nil, ""] do + defp parse_subject(%{"subject" => subject}) when is_binary(subject) and 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 + defp parse_forum_id(conn, %{"forum_id" => forum_id}) when is_binary(forum_id) and forum_id not in [nil, ""] do with {forum_id, _rest} <- Integer.parse(forum_id), true <- valid_forum?(conn.assigns.current_user, forum_id) do @@ -83,7 +83,7 @@ defmodule PhilomenaWeb.PostController do end defp parse_forum_id(_conn, _params), do: [] - defp parse_body(%{"body" => body}) when body not in [nil, ""], + defp parse_body(%{"body" => body}) when is_binary(body) and body not in [nil, ""], do: [%{match: %{body: body}}] defp parse_body(_params), do: [] diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 2ff8fc78..4c512638 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -100,6 +100,7 @@ defmodule PhilomenaWeb.Router do resources "/captchas", CaptchaController, only: [:create] resources "/posts", PostController, only: [:index] resources "/commissions", CommissionController, only: [:index, :show] + resources "/galleries", GalleryController, only: [:index, :show] get "/:id", ImageController, :show end diff --git a/lib/philomena_web/templates/gallery/_gallery.html.slime b/lib/philomena_web/templates/gallery/_gallery.html.slime new file mode 100644 index 00000000..05cf96e9 --- /dev/null +++ b/lib/philomena_web/templates/gallery/_gallery.html.slime @@ -0,0 +1,10 @@ +.media-box + a.media-box__header.media-box__header--link href=Routes.gallery_path(@conn, :show, @gallery) title=@gallery.title + = @gallery.title + + .media-box__overlay + = @gallery.spoiler_warning + + .media-box__content.media-box__content--large + a href=Routes.gallery_path(@conn, :show, @gallery) + = render PhilomenaWeb.ImageView, "_image_container.html", image: @gallery.thumbnail, size: :thumb, conn: @conn \ No newline at end of file diff --git a/lib/philomena_web/templates/gallery/index.html.slime b/lib/philomena_web/templates/gallery/index.html.slime new file mode 100644 index 00000000..e53b2626 --- /dev/null +++ b/lib/philomena_web/templates/gallery/index.html.slime @@ -0,0 +1,42 @@ +elixir: + route = fn p -> Routes.gallery_path(@conn, :index, p) end + pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @galleries, route: route + +.column-layout + .column-layout__left + .block + .block__content + h3 Search Galleries + + = form_for @conn, Routes.gallery_path(@conn, :index), [as: :gallery, method: "get", class: "hform"], fn f -> + .field = label f, :title, "Title" + .field = text_input f, :title, class: "input hform__text", placeholder: "Gallery title (* as wildcard)" + + .field = label f, :description, "Description" + .field = textarea f, :description, class: "input hform__text", placeholder: "Gallery description" + + .field = label f, :creator, "Creator" + .field = text_input f, :creator, class: "input hform__text", placeholder: "Gallery creator (exact match)" + + .field = label f, :include_image, "Include image" + .field = number_input f, :include_image, class: "input hform__text", placeholder: "Image ID (e.g. 100)" + + .field = label f, :sf, "Sort by" + .field + => select f, :sf, ["Creation Date": "created_at", "Update Date": "updated_at", "Image Count": "image_count", "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 + + .block__content.js-resizable-media-container + = for gallery <- @galleries do + = render PhilomenaWeb.GalleryView, "_gallery.html", gallery: gallery, conn: @conn + + .block__header.block__header--light + = pagination \ No newline at end of file diff --git a/lib/philomena_web/views/gallery_view.ex b/lib/philomena_web/views/gallery_view.ex new file mode 100644 index 00000000..20bce357 --- /dev/null +++ b/lib/philomena_web/views/gallery_view.ex @@ -0,0 +1,3 @@ +defmodule PhilomenaWeb.GalleryView do + use PhilomenaWeb, :view +end