This commit is contained in:
mdashlw 2025-03-27 04:05:19 +02:00 committed by GitHub
commit d9817b9714
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 84 additions and 25 deletions
lib
philomena
filters
images
philomena_query/ecto
philomena_web/plugs

View file

@ -49,8 +49,8 @@ defmodule Philomena.Filters.Filter do
|> validate_required([:name])
|> validate_my_downvotes(:spoilered_complex_str)
|> validate_my_downvotes(:hidden_complex_str)
|> validate_query(:spoilered_complex_str, &Query.compile(&1, user: user))
|> validate_query(:hidden_complex_str, &Query.compile(&1, user: user))
|> validate_query(:spoilered_complex_str, &Query.compile(&1, user: user, filter: true))
|> validate_query(:hidden_complex_str, &Query.compile(&1, user: user, filter: true))
|> unsafe_validate_unique([:user_id, :name], Repo)
end

View file

@ -1,6 +1,8 @@
defmodule Philomena.Images.Query do
alias PhilomenaQuery.Parse.Parser
alias Philomena.Repo
alias Philomena.Filters.Filter
import Ecto.Query
defp gallery_id_transform(_ctx, value) do
case Integer.parse(value) do
@ -12,6 +14,29 @@ defmodule Philomena.Images.Query do
end
end
defp filter_id_transform(%{filter: true}, _value),
do: {:error, "Filter queries inside filters are not allowed."}
defp filter_id_transform(%{user: user} = ctx, value) do
with {value, ""} <- Integer.parse(value),
{:ok, filter} <- Map.fetch(ctx.filters, value),
true <- Canada.Can.can?(user, :show, filter) do
ctx = Map.merge(ctx, %{filter: true})
{:ok,
%{
bool: %{
must_not: [
%{terms: %{tag_ids: filter.hidden_tag_ids}},
invalid_filter_guard(ctx, filter.hidden_complex_str)
]
}
}}
else
_ -> {:error, "Invalid filter `#{value}`."}
end
end
defp user_my_transform(%{user: %{id: id}}, "faves"),
do: {:ok, %{term: %{favourited_by_user_ids: id}}}
@ -59,8 +84,8 @@ defmodule Philomena.Images.Query do
defp user_my_transform(_ctx, _value),
do: {:error, "Unknown `my' value."}
defp invalid_filter_guard(ctx, search_string) do
case parse(user_fields(), ctx, PhilomenaQuery.Parse.String.normalize(search_string)) do
defp invalid_filter_guard(%{user: user} = ctx, search_string) do
case parse(fields_for(user), ctx, PhilomenaQuery.Parse.String.normalize(search_string)) do
{:ok, query} -> query
_error -> %{match_all: %{}}
end
@ -92,9 +117,12 @@ defmodule Philomena.Images.Query do
~W(faved_by orig_sha512_hash sha512_hash uploader source_url original_format mime_type file_name),
bool_fields: ~W(animated processed thumbnails_generated),
ngram_fields: ~W(description),
custom_fields: ~W(gallery_id),
custom_fields: ~W(gallery_id filter_id),
default_field: {"namespaced_tags.name", :term},
transforms: %{"gallery_id" => &gallery_id_transform/2},
transforms: %{
"gallery_id" => &gallery_id_transform/2,
"filter_id" => &filter_id_transform/2
},
aliases: %{
"faved_by" => "favourited_by_users",
"faved_by_id" => "favourited_by_user_ids"
@ -138,28 +166,56 @@ defmodule Philomena.Images.Query do
)
end
defp parse(fields, context, query_string) do
fields
|> Parser.new()
|> Parser.parse(query_string, context)
defp parse_filter_ids(query_string),
do:
Regex.scan(~r/\bfilter_id:(\d+)/, query_string, capture: :all_but_first)
|> List.flatten()
|> Enum.map(&String.to_integer/1)
|> Enum.filter(&(&1 <= 2_147_483_647))
defp load_filters([], _context), do: {:ok, %{}}
defp load_filters(_ids, %{filter: true}),
do: {:error, "Filter queries inside filters are not allowed."}
defp load_filters(ids, _context) do
filters =
Filter
|> where([f], f.id in ^ids)
|> Repo.all()
|> Map.new(&{&1.id, &1})
{:ok, filters}
end
defp prepare_context(context, query_string) do
with {:ok, filters} <- query_string |> parse_filter_ids() |> load_filters(context) do
{:ok, Map.merge(context, %{filters: filters})}
end
end
defp parse(fields, context, query_string) do
case prepare_context(context, query_string) do
{:ok, context} ->
fields
|> Parser.new()
|> Parser.parse(query_string, context)
{:error, error} ->
{:error, error}
end
end
defp fields_for(nil), do: anonymous_fields()
defp fields_for(%{role: role}) when role in ~W(user assistant), do: user_fields()
defp fields_for(%{role: role}) when role in ~W(moderator admin), do: moderator_fields()
defp fields_for(_), do: raise(ArgumentError, "Unknown user role.")
def compile(query_string, opts \\ []) do
user = Keyword.get(opts, :user)
watch = Keyword.get(opts, :watch, false)
filter = Keyword.get(opts, :filter, false)
case user do
nil ->
parse(anonymous_fields(), %{user: nil, watch: watch}, query_string)
%{role: role} when role in ~W(user assistant) ->
parse(user_fields(), %{user: user, watch: watch}, query_string)
%{role: role} when role in ~W(moderator admin) ->
parse(moderator_fields(), %{user: user, watch: watch}, query_string)
_ ->
raise ArgumentError, "Unknown user role."
end
parse(fields_for(user), %{user: user, watch: watch, filter: filter}, query_string)
end
end

View file

@ -62,6 +62,9 @@ defmodule PhilomenaQuery.Ecto.QueryValidator do
{:ok, _} <- callback.(value) do
changeset
else
{:error, msg} ->
add_error(changeset, attr, "is invalid: #{msg}")
_ ->
add_error(changeset, attr, "is invalid")
end

View file

@ -55,7 +55,7 @@ defmodule PhilomenaWeb.FilterForcedUsersPlug do
defp compile_filter(user, search_string) do
search_string
|> String.normalize()
|> Query.compile(user: user)
|> Query.compile(user: user, filter: true)
|> case do
{:ok, query} -> query
_error -> %{match_all: %{}}

View file

@ -51,7 +51,7 @@ defmodule PhilomenaWeb.ImageFilterPlug do
defp invalid_filter_guard(user, search_string) do
search_string
|> String.normalize()
|> Query.compile(user: user)
|> Query.compile(user: user, filter: true)
|> case do
{:ok, query} -> query
_error -> %{match_all: %{}}