mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-24 04:27:59 +01:00
Filter updates
This commit is contained in:
parent
61cafb9054
commit
9294e54771
17 changed files with 212 additions and 16 deletions
|
@ -215,6 +215,18 @@ defmodule Philomena.Users do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def force_filter(%User{} = user, user_params) do
|
||||||
|
user
|
||||||
|
|> User.force_filter_changeset(user_params)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def unforce_filter(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> User.unforce_filter_changeset()
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,7 @@ defmodule Philomena.Users.User do
|
||||||
many_to_many :roles, Role, join_through: "users_roles", on_replace: :delete
|
many_to_many :roles, Role, join_through: "users_roles", on_replace: :delete
|
||||||
|
|
||||||
belongs_to :current_filter, Filter
|
belongs_to :current_filter, Filter
|
||||||
|
belongs_to :forced_filter, Filter
|
||||||
belongs_to :deleted_by_user, User
|
belongs_to :deleted_by_user, User
|
||||||
|
|
||||||
# Authentication
|
# Authentication
|
||||||
|
@ -308,6 +309,16 @@ defmodule Philomena.Users.User do
|
||||||
put_api_key(user)
|
put_api_key(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def force_filter_changeset(user, params) do
|
||||||
|
user
|
||||||
|
|> cast(params, [:forced_filter_id])
|
||||||
|
|> foreign_key_constraint(:forced_filter_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unforce_filter_changeset(user) do
|
||||||
|
change(user, forced_filter_id: nil)
|
||||||
|
end
|
||||||
|
|
||||||
def create_totp_secret_changeset(user) do
|
def create_totp_secret_changeset(user) do
|
||||||
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
|
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
|
||||||
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
defmodule PhilomenaWeb.Admin.User.ForceFilterController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Users
|
||||||
|
|
||||||
|
plug :verify_authorized
|
||||||
|
plug :load_resource, model: User, id_name: "user_id", id_field: "slug", persisted: true
|
||||||
|
|
||||||
|
def new(conn, _params) do
|
||||||
|
changeset = Users.change_user(conn.assigns.user)
|
||||||
|
|
||||||
|
render(conn, "new.html", changeset: changeset, title: "Forcing filter for user")
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"user" => user_params}) do
|
||||||
|
{:ok, user} = Users.force_filter(conn.assigns.user, user_params)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Filter was forced.")
|
||||||
|
|> redirect(to: Routes.profile_path(conn, :show, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, _params) do
|
||||||
|
{:ok, user} = Users.unforce_filter(conn.assigns.current_user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Forced filter was removed.")
|
||||||
|
|> redirect(to: Routes.profile_path(conn, :show, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp verify_authorized(conn, _opts) do
|
||||||
|
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
|
||||||
|
true -> conn
|
||||||
|
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,8 +16,9 @@ defmodule PhilomenaWeb.Image.CommentController do
|
||||||
edit: :create_comment,
|
edit: :create_comment,
|
||||||
update: :create_comment
|
update: :create_comment
|
||||||
|
|
||||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
|
||||||
plug :verify_authorized when action in [:show]
|
plug :verify_authorized when action in [:show]
|
||||||
|
plug PhilomenaWeb.FilterForcedUsersPlug when action in [:create, :edit, :update]
|
||||||
|
|
||||||
# Undo the previous private parameter screwery
|
# Undo the previous private parameter screwery
|
||||||
plug PhilomenaWeb.LoadCommentPlug, [param: "id", show_hidden: true] when action in [:show]
|
plug PhilomenaWeb.LoadCommentPlug, [param: "id", show_hidden: true] when action in [:show]
|
||||||
|
|
|
@ -8,7 +8,8 @@ defmodule PhilomenaWeb.Image.FaveController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
|
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
|
||||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
|
||||||
|
plug PhilomenaWeb.FilterForcedUsersPlug
|
||||||
|
|
||||||
def create(conn, _params) do
|
def create(conn, _params) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
|
@ -8,7 +8,8 @@ defmodule PhilomenaWeb.Image.VoteController do
|
||||||
|
|
||||||
plug PhilomenaWeb.FilterBannedUsersPlug
|
plug PhilomenaWeb.FilterBannedUsersPlug
|
||||||
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
|
plug PhilomenaWeb.CanaryMapPlug, create: :vote, delete: :vote
|
||||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true, preload: [:tags]
|
||||||
|
plug PhilomenaWeb.FilterForcedUsersPlug
|
||||||
|
|
||||||
def create(conn, params) do
|
def create(conn, params) do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
|
@ -223,8 +223,9 @@ defmodule PhilomenaWeb.ProfileController do
|
||||||
defp set_admin_metadata(conn, _opts) do
|
defp set_admin_metadata(conn, _opts) do
|
||||||
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
|
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
|
||||||
true ->
|
true ->
|
||||||
user = Repo.preload(conn.assigns.user, :current_filter)
|
user = Repo.preload(conn.assigns.user, [:current_filter, :forced_filter])
|
||||||
filter = user.current_filter
|
filter = user.current_filter
|
||||||
|
forced = user.forced_filter
|
||||||
|
|
||||||
last_ip =
|
last_ip =
|
||||||
UserIp
|
UserIp
|
||||||
|
@ -242,6 +243,7 @@ defmodule PhilomenaWeb.ProfileController do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:filter, filter)
|
|> assign(:filter, filter)
|
||||||
|
|> assign(:forced, forced)
|
||||||
|> assign(:last_ip, last_ip)
|
|> assign(:last_ip, last_ip)
|
||||||
|> assign(:last_fp, last_fp)
|
|> assign(:last_fp, last_fp)
|
||||||
|
|
||||||
|
|
|
@ -12,22 +12,25 @@ defmodule PhilomenaWeb.CurrentFilterPlug do
|
||||||
conn = conn |> fetch_session()
|
conn = conn |> fetch_session()
|
||||||
user = conn |> Plug.current_user()
|
user = conn |> Plug.current_user()
|
||||||
|
|
||||||
filter =
|
{filter, forced_filter} =
|
||||||
if user do
|
if user do
|
||||||
|
user =
|
||||||
user
|
user
|
||||||
|> Repo.preload(:current_filter)
|
|> Repo.preload([:current_filter, :forced_filter])
|
||||||
|> maybe_set_default_filter()
|
|> maybe_set_default_filter()
|
||||||
|> Map.get(:current_filter)
|
|
||||||
|
{user.current_filter, user.forced_filter}
|
||||||
else
|
else
|
||||||
filter_id = conn |> get_session(:filter_id)
|
filter_id = conn |> get_session(:filter_id)
|
||||||
|
|
||||||
filter = if filter_id, do: Repo.get(Filter, filter_id)
|
filter = if filter_id, do: Repo.get(Filter, filter_id)
|
||||||
|
|
||||||
filter || Filters.default_filter()
|
{filter || Filters.default_filter(), nil}
|
||||||
end
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:current_filter, filter)
|
|> assign(:current_filter, filter)
|
||||||
|
|> assign(:forced_filter, forced_filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp maybe_set_default_filter(%{current_filter: nil} = user) do
|
defp maybe_set_default_filter(%{current_filter: nil} = user) do
|
||||||
|
|
59
lib/philomena_web/plugs/filter_forced_users_plug.ex
Normal file
59
lib/philomena_web/plugs/filter_forced_users_plug.ex
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
defmodule PhilomenaWeb.FilterForcedUsersPlug do
|
||||||
|
@moduledoc """
|
||||||
|
Halts the request pipeline if the current image belongs to the conn's
|
||||||
|
"forced filter".
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Phoenix.Controller
|
||||||
|
import Plug.Conn
|
||||||
|
alias Philomena.Search.String, as: SearchString
|
||||||
|
alias Philomena.Search.Evaluator
|
||||||
|
alias Philomena.Images.Query
|
||||||
|
alias PhilomenaWeb.ImageView
|
||||||
|
|
||||||
|
def init(_opts) do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
maybe_fetch_forced(conn, conn.assigns.forced_filter)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_fetch_forced(conn, nil), do: conn
|
||||||
|
defp maybe_fetch_forced(conn, forced) do
|
||||||
|
maybe_halt(conn, matches_filter?(conn.assigns.current_user, conn.assigns.image, forced))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_halt(conn, false), do: conn
|
||||||
|
defp maybe_halt(conn, true) do
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "You have been blocked from performing this action on this image.")
|
||||||
|
|> redirect(external: conn.assigns.referrer)
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp matches_filter?(user, image, filter) do
|
||||||
|
matches_tag_filter?(image, filter.hidden_tag_ids) or
|
||||||
|
matches_complex_filter?(user, image, filter.hidden_complex_str)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp matches_tag_filter?(image, tag_ids) do
|
||||||
|
image.tags
|
||||||
|
|> MapSet.new(& &1.id)
|
||||||
|
|> MapSet.intersection(MapSet.new(tag_ids))
|
||||||
|
|> Enum.any?()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp matches_complex_filter?(user, image, search_string) do
|
||||||
|
image
|
||||||
|
|> ImageView.image_filter_data()
|
||||||
|
|> Evaluator.hits?(compile_filter(user, search_string))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp compile_filter(user, search_string) do
|
||||||
|
case Query.compile(user, SearchString.normalize(search_string)) do
|
||||||
|
{:ok, query} -> query
|
||||||
|
_error -> %{match_all: %{}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -11,11 +11,19 @@ defmodule PhilomenaWeb.ImageFilterPlug do
|
||||||
# Assign current filter
|
# Assign current filter
|
||||||
def call(conn, _opts) do
|
def call(conn, _opts) do
|
||||||
user = conn |> Plug.current_user()
|
user = conn |> Plug.current_user()
|
||||||
filter = conn.assigns[:current_filter]
|
filter = defaults(conn.assigns[:current_filter])
|
||||||
|
forced = defaults(conn.assigns[:forced_filter])
|
||||||
|
|
||||||
tag_exclusion = %{terms: %{tag_ids: filter.hidden_tag_ids}}
|
tag_exclusion = %{terms: %{tag_ids: filter.hidden_tag_ids}}
|
||||||
query_exclusion = invalid_filter_guard(user, filter.hidden_complex_str)
|
|
||||||
query_spoiler = invalid_filter_guard(user, filter.spoilered_complex_str)
|
query_spoiler = invalid_filter_guard(user, filter.spoilered_complex_str)
|
||||||
|
query_exclusion = %{
|
||||||
|
bool: %{
|
||||||
|
should: [
|
||||||
|
invalid_filter_guard(user, filter.hidden_complex_str),
|
||||||
|
invalid_filter_guard(user, forced.hidden_complex_str)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
query = %{
|
query = %{
|
||||||
bool: %{
|
bool: %{
|
||||||
|
@ -29,6 +37,18 @@ defmodule PhilomenaWeb.ImageFilterPlug do
|
||||||
|> assign(:compiled_filter, query)
|
|> assign(:compiled_filter, query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp defaults(nil) do
|
||||||
|
%{
|
||||||
|
hidden_tag_ids: [],
|
||||||
|
hidden_complex_str: nil,
|
||||||
|
spoilered_complex_str: nil
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp defaults(filter) do
|
||||||
|
filter
|
||||||
|
end
|
||||||
|
|
||||||
defp invalid_filter_guard(user, search_string) do
|
defp invalid_filter_guard(user, search_string) do
|
||||||
case Query.compile(user, normalize(search_string)) do
|
case Query.compile(user, normalize(search_string)) do
|
||||||
{:ok, query} -> query
|
{:ok, query} -> query
|
||||||
|
|
|
@ -366,6 +366,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
|
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
|
||||||
resources "/votes", User.VoteController, only: [:delete], singleton: true
|
resources "/votes", User.VoteController, only: [:delete], singleton: true
|
||||||
resources "/wipe", User.WipeController, only: [:create], singleton: true
|
resources "/wipe", User.WipeController, only: [:create], singleton: true
|
||||||
|
resources "/force_filter", User.ForceFilterController, only: [:new, :create, :delete], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/batch/tags", Batch.TagController, only: [:update], singleton: true
|
resources "/batch/tags", Batch.TagController, only: [:update], singleton: true
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
h1
|
||||||
|
' Force-assigning a filter for user
|
||||||
|
= @user.name
|
||||||
|
|
||||||
|
= form_for @changeset, Routes.admin_user_force_filter_path(@conn, :create, @user), [method: "post"], fn f ->
|
||||||
|
.field
|
||||||
|
=> text_input f, :forced_filter_id, placeholder: "Filter ID", class: "input", required: true
|
||||||
|
.field
|
||||||
|
= submit "Force", class: "button button--state-primary"
|
|
@ -12,6 +12,12 @@
|
||||||
em
|
em
|
||||||
' (none)
|
' (none)
|
||||||
|
|
||||||
|
= if @forced do
|
||||||
|
br
|
||||||
|
i.fa.fa-fw.fa-filter>
|
||||||
|
strong.comment_deleted> Forced Filter:
|
||||||
|
= link @forced.name, to: Routes.filter_path(@conn, :show, @filter)
|
||||||
|
|
||||||
br
|
br
|
||||||
i.far.fa-fw.fa-clock>
|
i.far.fa-fw.fa-clock>
|
||||||
' Last seen
|
' Last seen
|
||||||
|
@ -78,6 +84,17 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
|
||||||
i.fas.fa-fw.fa-edit
|
i.fas.fa-fw.fa-edit
|
||||||
span.admin__button Edit User
|
span.admin__button Edit User
|
||||||
|
|
||||||
|
li
|
||||||
|
= link to: Routes.admin_user_force_filter_path(@conn, :new, @user) do
|
||||||
|
i.fas.faw-fw.fa-filter
|
||||||
|
span.admin__button Force Filter
|
||||||
|
|
||||||
|
= if @forced do
|
||||||
|
li
|
||||||
|
= link to: Routes.admin_user_force_filter_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
|
||||||
|
i.fas.faw-fw.fa-filter
|
||||||
|
span.admin__button Remove Force Filter
|
||||||
|
|
||||||
= if @user.deleted_at do
|
= if @user.deleted_at do
|
||||||
li
|
li
|
||||||
= link to: Routes.admin_user_activation_path(@conn, :create, @user), data: [confirm: "Are you really, really sure?", method: "post"] do
|
= link to: Routes.admin_user_activation_path(@conn, :create, @user), data: [confirm: "Are you really, really sure?", method: "post"] do
|
||||||
|
|
3
lib/philomena_web/views/admin/user/force_filter_view.ex
Normal file
3
lib/philomena_web/views/admin/user/force_filter_view.ex
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
defmodule PhilomenaWeb.Admin.User.ForceFilterView do
|
||||||
|
use PhilomenaWeb, :view
|
||||||
|
end
|
|
@ -216,7 +216,7 @@ defmodule PhilomenaWeb.ImageView do
|
||||||
defp thumb_format(_, :rendered, _download), do: "png"
|
defp thumb_format(_, :rendered, _download), do: "png"
|
||||||
defp thumb_format(format, _name, _download), do: format
|
defp thumb_format(format, _name, _download), do: format
|
||||||
|
|
||||||
defp image_filter_data(image) do
|
def image_filter_data(image) do
|
||||||
%{
|
%{
|
||||||
id: image.id,
|
id: image.id,
|
||||||
"namespaced_tags.name": String.split(image.tag_list_plus_alias_cache || "", ", "),
|
"namespaced_tags.name": String.split(image.tag_list_plus_alias_cache || "", ", "),
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule Philomena.Repo.Migrations.AddForcedFilter do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:users) do
|
||||||
|
add :forced_filter_id, references(:filters), on_delete: :restrict
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,7 @@
|
||||||
--
|
--
|
||||||
|
|
||||||
-- Dumped from database version 12.2 (Debian 12.2-2.pgdg100+1)
|
-- Dumped from database version 12.2 (Debian 12.2-2.pgdg100+1)
|
||||||
-- Dumped by pg_dump version 12.2 (Debian 12.2-2.pgdg90+1)
|
-- Dumped by pg_dump version 12.3 (Debian 12.3-1.pgdg90+1)
|
||||||
|
|
||||||
SET statement_timeout = 0;
|
SET statement_timeout = 0;
|
||||||
SET lock_timeout = 0;
|
SET lock_timeout = 0;
|
||||||
|
@ -1976,7 +1976,8 @@ CREATE TABLE public.users (
|
||||||
consumed_timestep integer,
|
consumed_timestep integer,
|
||||||
otp_required_for_login boolean,
|
otp_required_for_login boolean,
|
||||||
otp_backup_codes character varying[],
|
otp_backup_codes character varying[],
|
||||||
last_renamed_at timestamp without time zone DEFAULT '1970-01-01 00:00:00'::timestamp without time zone NOT NULL
|
last_renamed_at timestamp without time zone DEFAULT '1970-01-01 00:00:00'::timestamp without time zone NOT NULL,
|
||||||
|
forced_filter_id bigint
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -4709,9 +4710,17 @@ ALTER TABLE ONLY public.image_sources
|
||||||
ADD CONSTRAINT image_sources_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id);
|
ADD CONSTRAINT image_sources_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id);
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Name: users users_forced_filter_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||||
|
--
|
||||||
|
|
||||||
|
ALTER TABLE ONLY public.users
|
||||||
|
ADD CONSTRAINT users_forced_filter_id_fkey FOREIGN KEY (forced_filter_id) REFERENCES public.filters(id);
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20200503002523);
|
INSERT INTO public."schema_migrations" (version) VALUES (20200503002523), (20200607000511);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue