mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-20 20:34:23 +01:00
Move commission searching to context
This commit is contained in:
parent
10616da04f
commit
e840ed2618
6 changed files with 184 additions and 93 deletions
|
@ -8,6 +8,9 @@ defmodule Philomena.Commissions do
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
|
|
||||||
alias Philomena.Commissions.Commission
|
alias Philomena.Commissions.Commission
|
||||||
|
alias Philomena.Commissions.Item
|
||||||
|
alias Philomena.Commissions.QueryBuilder
|
||||||
|
alias Philomena.Commissions.SearchQuery
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single commission.
|
Gets a single commission.
|
||||||
|
@ -90,7 +93,37 @@ defmodule Philomena.Commissions do
|
||||||
Commission.changeset(commission, %{})
|
Commission.changeset(commission, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
alias Philomena.Commissions.Item
|
@doc """
|
||||||
|
Searches commissions based on the given parameters.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* params - Map of optional search parameters:
|
||||||
|
* item_type - Filter by item type
|
||||||
|
* category - Filter by category
|
||||||
|
* keywords - Search in information and will_create fields
|
||||||
|
* price_min - Minimum base price
|
||||||
|
* price_max - Maximum base price
|
||||||
|
|
||||||
|
Returns `{:ok, query}` with a queryable that can be used with Repo.paginate/2,
|
||||||
|
or `{:error, changeset}` if the provided parameters are invalid.
|
||||||
|
"""
|
||||||
|
def execute_search_query(params \\ %{}) do
|
||||||
|
QueryBuilder.search_commissions(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking search query changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_search_query(search_query)
|
||||||
|
%Ecto.Changeset{source: %SearchQuery{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_search_query(%SearchQuery{} = search_query) do
|
||||||
|
SearchQuery.changeset(search_query, %{})
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single item.
|
Gets a single item.
|
||||||
|
|
110
lib/philomena/commissions/query_builder.ex
Normal file
110
lib/philomena/commissions/query_builder.ex
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
defmodule Philomena.Commissions.QueryBuilder do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
alias Philomena.Commissions.Commission
|
||||||
|
alias Philomena.Commissions.Item
|
||||||
|
alias Philomena.Commissions.SearchQuery
|
||||||
|
alias Philomena.UserIps.UserIp
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Searches commissions based on the given parameters.
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
* params - Map of optional search parameters:
|
||||||
|
* item_type - Filter by item type
|
||||||
|
* category - Filter by category
|
||||||
|
* keywords - Search in information and will_create fields
|
||||||
|
* price_min - Minimum base price
|
||||||
|
* price_max - Maximum base price
|
||||||
|
|
||||||
|
Returns `{:ok, query}` with a queryable that can be used with Repo.paginate/2,
|
||||||
|
or `{:error, changeset}` if the provided parameters are invalid.
|
||||||
|
"""
|
||||||
|
def search_commissions(params \\ %{}) do
|
||||||
|
%SearchQuery{}
|
||||||
|
|> SearchQuery.changeset(params)
|
||||||
|
|> Ecto.Changeset.apply_action(:create)
|
||||||
|
|> case do
|
||||||
|
{:ok, sq} ->
|
||||||
|
{:ok,
|
||||||
|
commission_search_query()
|
||||||
|
|> maybe_filter_price(sq)
|
||||||
|
|> maybe_filter_item_type(sq)
|
||||||
|
|> maybe_filter_categories(sq)
|
||||||
|
|> maybe_filter_keywords(sq)}
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
{:error, changeset}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp commission_search_query do
|
||||||
|
# Select commissions and all of their associated items for filtering
|
||||||
|
query =
|
||||||
|
from c in Commission,
|
||||||
|
as: :commission,
|
||||||
|
where: c.open == true,
|
||||||
|
where: c.commission_items_count > 0,
|
||||||
|
inner_join: ci in Item,
|
||||||
|
as: :commission_item,
|
||||||
|
on: ci.commission_id == c.id
|
||||||
|
|
||||||
|
# Exclude artists with no activity in the last 2 weeks
|
||||||
|
query =
|
||||||
|
from [commission: c] in query,
|
||||||
|
inner_join: ui in UserIp,
|
||||||
|
as: :user_ip,
|
||||||
|
on: ui.user_id == c.user_id,
|
||||||
|
where: ui.updated_at >= ago(2, "week")
|
||||||
|
|
||||||
|
# Select the parent commissions, not the items belonging to them
|
||||||
|
from [commission: c] in query,
|
||||||
|
group_by: c.id,
|
||||||
|
order_by: [asc: fragment("random()")],
|
||||||
|
preload: [user: [awards: :badge], items: [example_image: [:sources, tags: :aliases]]]
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_price(query, sq = %SearchQuery{}) do
|
||||||
|
if not is_nil(sq.price_min) and not is_nil(sq.price_max) do
|
||||||
|
from [commission_item: ci] in query,
|
||||||
|
where: ci.base_price >= ^sq.price_min and ci.base_price <= ^sq.price_max
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_filter_item_type(query, sq = %SearchQuery{}) do
|
||||||
|
if sq.item_type do
|
||||||
|
from [commission_item: ci] in query,
|
||||||
|
where: ci.item_type == ^sq.item_type
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_categories(query, sq = %SearchQuery{}) do
|
||||||
|
if sq.category do
|
||||||
|
from [commission: c] in query,
|
||||||
|
where: fragment("? @> ?", c.categories, ^sq.category)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_filter_keywords(query, sq = %SearchQuery{}) do
|
||||||
|
if sq.keywords do
|
||||||
|
keywords = like_sanitize(sq.keywords)
|
||||||
|
|
||||||
|
from [commission: c] in query,
|
||||||
|
where: ilike(c.information, ^keywords) or ilike(c.will_create, ^keywords)
|
||||||
|
else
|
||||||
|
query
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp like_sanitize(input) do
|
||||||
|
"%" <> String.replace(input, ["\\", "%", "_"], &<<"\\", &1>>) <> "%"
|
||||||
|
end
|
||||||
|
end
|
19
lib/philomena/commissions/search_query.ex
Normal file
19
lib/philomena/commissions/search_query.ex
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Philomena.Commissions.SearchQuery do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field :item_type, :string
|
||||||
|
field :category, {:array, :string}
|
||||||
|
field :keywords, :string
|
||||||
|
field :price_min, :decimal
|
||||||
|
field :price_max, :decimal
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
def changeset(query, params) do
|
||||||
|
cast(query, params, [:item_type, :category, :keywords, :price_min, :price_max])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,105 +1,35 @@
|
||||||
defmodule PhilomenaWeb.CommissionController do
|
defmodule PhilomenaWeb.CommissionController do
|
||||||
use PhilomenaWeb, :controller
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
alias Philomena.Commissions.{Item, Commission}
|
alias Philomena.Commissions.SearchQuery
|
||||||
alias Philomena.UserIps.UserIp
|
alias Philomena.Commissions
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
plug PhilomenaWeb.MapParameterPlug, [param: "commission"] when action in [:index]
|
plug PhilomenaWeb.MapParameterPlug, [param: "commission"] when action in [:index]
|
||||||
plug :preload_commission
|
plug :preload_commission
|
||||||
|
|
||||||
def index(conn, params) do
|
def index(conn, params) do
|
||||||
commissions =
|
commission_params = Map.get(params, "commission", %{})
|
||||||
commission_search(params["commission"])
|
|
||||||
|> Repo.paginate(conn.assigns.scrivener)
|
{commissions, changeset} =
|
||||||
|
case Commissions.execute_search_query(commission_params) do
|
||||||
|
{:ok, commissions} ->
|
||||||
|
commissions = Repo.paginate(commissions, conn.assigns.scrivener)
|
||||||
|
changeset = Commissions.change_search_query(%SearchQuery{})
|
||||||
|
{commissions, changeset}
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
{[], changeset}
|
||||||
|
end
|
||||||
|
|
||||||
render(conn, "index.html",
|
render(conn, "index.html",
|
||||||
title: "Commissions",
|
title: "Commissions",
|
||||||
commissions: commissions,
|
commissions: commissions,
|
||||||
|
changeset: changeset,
|
||||||
layout_class: "layout--wide"
|
layout_class: "layout--wide"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp commission_search(attrs) when is_map(attrs) do
|
|
||||||
item_type = presence(attrs["item_type"])
|
|
||||||
categories = presence(attrs["category"])
|
|
||||||
keywords = presence(attrs["keywords"])
|
|
||||||
price_min = to_f(presence(attrs["price_min"]) || 0)
|
|
||||||
price_max = to_f(presence(attrs["price_max"]) || 9999)
|
|
||||||
|
|
||||||
query =
|
|
||||||
commission_search(nil)
|
|
||||||
|> where([_c, ci], ci.base_price > ^price_min and ci.base_price < ^price_max)
|
|
||||||
|
|
||||||
query =
|
|
||||||
if item_type do
|
|
||||||
query
|
|
||||||
|> where([_c, ci], ci.item_type == ^item_type)
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
|
|
||||||
query =
|
|
||||||
if categories do
|
|
||||||
query
|
|
||||||
|> where([c, _ci], fragment("? @> ?", c.categories, ^categories))
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
|
|
||||||
query =
|
|
||||||
if keywords do
|
|
||||||
query
|
|
||||||
|> where(
|
|
||||||
[c, _ci],
|
|
||||||
ilike(c.information, ^like_sanitize(keywords)) or
|
|
||||||
ilike(c.will_create, ^like_sanitize(keywords))
|
|
||||||
)
|
|
||||||
else
|
|
||||||
query
|
|
||||||
end
|
|
||||||
|
|
||||||
query
|
|
||||||
end
|
|
||||||
|
|
||||||
defp commission_search(_attrs) do
|
|
||||||
from c in Commission,
|
|
||||||
where: c.open == true,
|
|
||||||
where: c.commission_items_count > 0,
|
|
||||||
inner_join: ci in Item,
|
|
||||||
on: ci.commission_id == c.id,
|
|
||||||
inner_join: ui in UserIp,
|
|
||||||
on: ui.user_id == c.user_id,
|
|
||||||
where: ui.updated_at >= ago(2, "week"),
|
|
||||||
group_by: c.id,
|
|
||||||
order_by: [asc: fragment("random()")],
|
|
||||||
preload: [user: [awards: :badge], items: [example_image: [:sources, tags: :aliases]]]
|
|
||||||
end
|
|
||||||
|
|
||||||
defp presence(nil),
|
|
||||||
do: nil
|
|
||||||
|
|
||||||
defp presence([]),
|
|
||||||
do: nil
|
|
||||||
|
|
||||||
defp presence(string) when is_binary(string),
|
|
||||||
do: if(String.trim(string) == "", do: nil, else: string)
|
|
||||||
|
|
||||||
defp presence(object),
|
|
||||||
do: object
|
|
||||||
|
|
||||||
defp to_f(input) do
|
|
||||||
case Float.parse(to_string(input)) do
|
|
||||||
{float, _rest} -> float
|
|
||||||
_error -> 0.0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp like_sanitize(input) do
|
|
||||||
"%" <> String.replace(input, ["\\", "%", "_"], &<<"\\", &1>>) <> "%"
|
|
||||||
end
|
|
||||||
|
|
||||||
defp preload_commission(conn, _opts) do
|
defp preload_commission(conn, _opts) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
.block__header
|
.block__header
|
||||||
span.block__header__title Search
|
span.block__header__title Search
|
||||||
.block__content
|
.block__content
|
||||||
= form_for @conn, ~p"/commissions", [as: :commission, method: "get", class: "hform"], fn f ->
|
= form_for @changeset, ~p"/commissions", [as: :commission, method: "get", class: "hform"], fn f ->
|
||||||
.field = label f, :categories, "Art Categories:"
|
.field = label f, :categories, "Art Categories:"
|
||||||
|
|
||||||
= for {name, value} <- categories() do
|
= for {name, _value} <- categories() do
|
||||||
- checked = @conn.params["commission"]["category"] && Atom.to_string(name) in @conn.params["commission"]["category"]
|
- checked = @conn.params["commission"]["category"] && Atom.to_string(name) in @conn.params["commission"]["category"]
|
||||||
.field
|
.field
|
||||||
=> checkbox f, value, checked_value: name, checked: checked, name: "commission[category][]", class: "checkbox spacing-right", hidden_input: false
|
=> checkbox f, name, checked_value: name, checked: checked, name: "commission[category][]", class: "checkbox spacing-right", hidden_input: false
|
||||||
=> label f, value, name
|
=> label f, name, name
|
||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,11 @@ h1 Commissions Directory
|
||||||
' commissions with potential commissioners. We don't have any way
|
' commissions with potential commissioners. We don't have any way
|
||||||
' for users to make payments through the site, so we can't be held
|
' for users to make payments through the site, so we can't be held
|
||||||
' responsible for any issues regarding payment.
|
' responsible for any issues regarding payment.
|
||||||
a href="/pages/rules#9" More info.
|
|
||||||
|
|
||||||
br
|
br
|
||||||
|
|
||||||
.column-layout
|
.column-layout
|
||||||
.column-layout__left
|
.column-layout__left
|
||||||
= render PhilomenaWeb.CommissionView, "_directory_sidebar.html", conn: @conn
|
= render PhilomenaWeb.CommissionView, "_directory_sidebar.html", changeset: @changeset, conn: @conn
|
||||||
.column-layout__main
|
.column-layout__main
|
||||||
= render PhilomenaWeb.CommissionView, "_directory_results.html", commissions: @commissions, conn: @conn
|
= render PhilomenaWeb.CommissionView, "_directory_results.html", commissions: @commissions, conn: @conn
|
||||||
|
|
Loading…
Reference in a new issue