Tag locking (#102)

This commit is contained in:
liamwhite 2021-03-01 12:01:27 -05:00 committed by GitHub
parent 7030b02183
commit f112f7928b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 162 additions and 20 deletions

View file

@ -265,15 +265,25 @@ defmodule Philomena.Images do
|> Repo.transaction() |> Repo.transaction()
end end
def update_locked_tags(%Image{} = image, attrs) do
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
image
|> Repo.preload(:locked_tags)
|> Image.locked_tags_changeset(attrs, new_tags)
|> Repo.update()
end
def update_tags(%Image{} = image, attribution, attrs) do def update_tags(%Image{} = image, attribution, attrs) do
old_tags = Tags.get_or_create_tags(attrs["old_tag_input"]) old_tags = Tags.get_or_create_tags(attrs["old_tag_input"])
new_tags = Tags.get_or_create_tags(attrs["tag_input"]) new_tags = Tags.get_or_create_tags(attrs["tag_input"])
Multi.new() Multi.new()
|> Multi.run(:image, fn repo, _chg -> |> Multi.run(:image, fn repo, _chg ->
image = repo.preload(image, [:tags, :locked_tags])
image image
|> repo.preload(:tags) |> Image.tag_changeset(%{}, old_tags, new_tags, image.locked_tags)
|> Image.tag_changeset(%{}, old_tags, new_tags)
|> repo.update() |> repo.update()
|> case do |> case do
{:ok, image} -> {:ok, image} ->

View file

@ -38,6 +38,7 @@ defmodule Philomena.Images.Image do
has_many :favers, through: [:faves, :user] has_many :favers, through: [:faves, :user]
has_many :hiders, through: [:hides, :user] has_many :hiders, through: [:hides, :user]
many_to_many :tags, Tag, join_through: "image_taggings", on_replace: :delete many_to_many :tags, Tag, join_through: "image_taggings", on_replace: :delete
many_to_many :locked_tags, Tag, join_through: "image_tag_locks", on_replace: :delete
has_one :intensity, ImageIntensity has_one :intensity, ImageIntensity
has_many :galleries, through: [:gallery_interactions, :image] has_many :galleries, through: [:gallery_interactions, :image]
@ -179,14 +180,20 @@ defmodule Philomena.Images.Image do
|> validate_format(:source_url, ~r/\Ahttps?:\/\//) |> validate_format(:source_url, ~r/\Ahttps?:\/\//)
end end
def tag_changeset(image, attrs, old_tags, new_tags) do def tag_changeset(image, attrs, old_tags, new_tags, excluded_tags \\ []) do
image image
|> cast(attrs, []) |> cast(attrs, [])
|> TagDiffer.diff_input(old_tags, new_tags) |> TagDiffer.diff_input(old_tags, new_tags, excluded_tags)
|> TagValidator.validate_tags() |> TagValidator.validate_tags()
|> cache_changeset() |> cache_changeset()
end end
def locked_tags_changeset(image, attrs, locked_tags) do
image
|> cast(attrs, [])
|> put_assoc(:locked_tags, locked_tags)
end
def dnp_changeset(image, user) do def dnp_changeset(image, user) do
image image
|> change() |> change()

View file

@ -5,13 +5,15 @@ defmodule Philomena.Images.TagDiffer do
alias Philomena.Tags.Tag alias Philomena.Tags.Tag
alias Philomena.Repo alias Philomena.Repo
def diff_input(changeset, old_tags, new_tags) do def diff_input(changeset, old_tags, new_tags, excluded_tags) do
excluded_ids = Enum.map(excluded_tags, & &1.id)
old_set = to_set(old_tags) old_set = to_set(old_tags)
new_set = to_set(new_tags) new_set = to_set(new_tags)
tags = changeset |> get_field(:tags) tags = changeset |> get_field(:tags)
added_tags = added_set(old_set, new_set) added_tags = added_set(old_set, new_set, excluded_ids)
removed_tags = removed_set(old_set, new_set) removed_tags = removed_set(old_set, new_set, excluded_ids)
{tags, actually_added, actually_removed} = apply_changes(tags, added_tags, removed_tags) {tags, actually_added, actually_removed} = apply_changes(tags, added_tags, removed_tags)
@ -21,7 +23,7 @@ defmodule Philomena.Images.TagDiffer do
|> put_assoc(:tags, tags) |> put_assoc(:tags, tags)
end end
defp added_set(old_set, new_set) do defp added_set(old_set, new_set, excluded_ids) do
# new_tags - old_tags # new_tags - old_tags
added_set = added_set =
new_set new_set
@ -40,12 +42,16 @@ defmodule Philomena.Images.TagDiffer do
|> Enum.filter(fn {_k, v} -> v.namespace == "oc" end) |> Enum.filter(fn {_k, v} -> v.namespace == "oc" end)
|> get_oc_tag() |> get_oc_tag()
Map.merge(added_and_implied_set, oc_set) added_and_implied_set
|> Map.merge(oc_set)
|> Map.drop(excluded_ids)
end end
defp removed_set(old_set, new_set) do defp removed_set(old_set, new_set, excluded_ids) do
# old_tags - new_tags # old_tags - new_tags
old_set |> Map.drop(Map.keys(new_set)) old_set
|> Map.drop(Map.keys(new_set))
|> Map.drop(excluded_ids)
end end
defp get_oc_tag([]), do: Map.new() defp get_oc_tag([]), do: Map.new()

View file

@ -0,0 +1,21 @@
defmodule Philomena.Images.TagLock do
use Ecto.Schema
import Ecto.Changeset
alias Philomena.Images.Image
alias Philomena.Tags.Tag
@primary_key false
schema "image_tag_locks" do
belongs_to :image, Image, primary_key: true
belongs_to :tag, Tag, primary_key: true
end
@doc false
def changeset(tag_lock, attrs) do
tag_lock
|> cast(attrs, [])
|> validate_required([])
end
end

View file

@ -23,7 +23,7 @@ defmodule PhilomenaWeb.Image.TagController do
plug :load_and_authorize_resource, plug :load_and_authorize_resource,
model: Image, model: Image,
id_name: "image_id", id_name: "image_id",
preload: [:user, tags: :aliases] preload: [:user, :locked_tags, tags: :aliases]
def update(conn, %{"image" => image_params}) do def update(conn, %{"image" => image_params}) do
attributes = conn.assigns.attributes attributes = conn.assigns.attributes

View file

@ -4,8 +4,27 @@ defmodule PhilomenaWeb.Image.TagLockController do
alias Philomena.Images.Image alias Philomena.Images.Image
alias Philomena.Images alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :hide, delete: :hide plug PhilomenaWeb.CanaryMapPlug, show: :hide, update: :hide, create: :hide, delete: :hide
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: [:locked_tags]
def show(conn, _params) do
changeset = Images.change_image(conn.assigns.image)
render(conn, "show.html", title: "Locking image tags", changeset: changeset)
end
def update(conn, %{"image" => image_attrs}) do
{:ok, image} = Images.update_locked_tags(conn.assigns.image, image_attrs)
conn
|> put_flash(:info, "Successfully updated list of locked tags.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
def create(conn, _params) do def create(conn, _params) do
{:ok, image} = Images.lock_tags(conn.assigns.image, true) {:ok, image} = Images.lock_tags(conn.assigns.image, true)

View file

@ -181,7 +181,7 @@ defmodule PhilomenaWeb.ImageController do
[i, _], [i, _],
_ in fragment("SELECT COUNT(*) FROM source_changes s WHERE s.image_id = ?", i.id) _ in fragment("SELECT COUNT(*) FROM source_changes s WHERE s.image_id = ?", i.id)
) )
|> preload([:deleter, user: [awards: :badge], tags: :aliases]) |> preload([:deleter, :locked_tags, user: [awards: :badge], tags: :aliases])
|> select([i, t, s], {i, t.count, s.count}) |> select([i, t, s], {i, t.count, s.count})
|> Repo.one() |> Repo.one()
|> case do |> case do

View file

@ -212,7 +212,9 @@ defmodule PhilomenaWeb.Router do
only: [:create, :delete], only: [:create, :delete],
singleton: true singleton: true
resources "/tag_lock", Image.TagLockController, only: [:create, :delete], singleton: true resources "/tag_lock", Image.TagLockController,
only: [:show, :update, :create, :delete],
singleton: true
end end
resources "/forums", ForumController, only: [] do resources "/forums", ForumController, only: [] do

View file

@ -142,7 +142,8 @@
- else - else
= button_to "Unlock tag editing", Routes.image_tag_lock_path(@conn, :delete, @image), method: "delete", class: "button" = button_to "Unlock tag editing", Routes.image_tag_lock_path(@conn, :delete, @image), method: "delete", class: "button"
= if @image.hidden_from_users and can?(@conn, :destroy, @image) do br
br .flex.flex--spaced-out
.flex.flex--spaced-out = link "Lock specific tags", to: Routes.image_tag_lock_path(@conn, :show, @image), class: "button"
= button_to "Destroy image", Routes.image_destroy_path(@conn, :create, @image), method: "post", class: "button button--state-danger", data: [confirm: "This action is IRREVERSIBLE. Are you sure?"] = if @image.hidden_from_users and can?(@conn, :destroy, @image) do
= button_to "Destroy image", Routes.image_destroy_path(@conn, :create, @image), method: "post", class: "button button--state-danger", data: [confirm: "This action is IRREVERSIBLE. Are you sure?"]

View file

@ -6,6 +6,12 @@
.js-imageform class=form_class .js-imageform class=form_class
= if can?(@conn, :edit_metadata, @image) and !@conn.assigns.current_ban do = if can?(@conn, :edit_metadata, @image) and !@conn.assigns.current_ban do
= if Enum.any?(@image.locked_tags) do
.block.block--fixed.block--warning
i.fa.fa-lock>
' The following tags have been restricted on this image:
code= Enum.map_join(@image.locked_tags, ", ", & &1.name)
= form_for @changeset, Routes.image_tag_path(@conn, :update, @image), [id: "tags-form", method: "put", data: [remote: true]], fn f -> = form_for @changeset, Routes.image_tag_path(@conn, :update, @image), [id: "tags-form", method: "put", data: [remote: true]], fn f ->
= if @changeset.action do = if @changeset.action do
.alert.alert-danger .alert.alert-danger

View file

@ -0,0 +1,13 @@
- tag_input = Enum.map_join(@image.locked_tags, ", ", & &1.name)
h1
| Editing locked tags on image #
= @image.id
= form_for @changeset, Routes.image_tag_lock_path(@conn, :update, @image), fn f ->
.field
= render PhilomenaWeb.TagView, "_tag_editor.html", f: f, name: :tag_input, type: :edit, extra: [value: tag_input]
= error_tag f, :tag_input
.actions
= submit "Update", class: "button", autocomplete: "off", data: [disable_with: "Please wait..."]

View file

@ -0,0 +1,3 @@
defmodule PhilomenaWeb.Image.TagLockView do
use PhilomenaWeb, :view
end

View file

@ -0,0 +1,13 @@
defmodule Philomena.Repo.Migrations.AddTagLocks do
use Ecto.Migration
def change do
create table("image_tag_locks", primary_key: false) do
add :image_id, references(:images, on_delete: :delete_all), null: false
add :tag_id, references(:tags, on_delete: :delete_all), null: false
end
create index("image_tag_locks", [:image_id, :tag_id], unique: true)
create index("image_tag_locks", [:tag_id])
end
end

View file

@ -858,6 +858,16 @@ CREATE TABLE public.image_subscriptions (
); );
--
-- Name: image_tag_locks; Type: TABLE; Schema: public; Owner: -
--
CREATE TABLE public.image_tag_locks (
image_id bigint NOT NULL,
tag_id bigint NOT NULL
);
-- --
-- Name: image_taggings; Type: TABLE; Schema: public; Owner: - -- Name: image_taggings; Type: TABLE; Schema: public; Owner: -
-- --
@ -2796,6 +2806,20 @@ CREATE INDEX image_intensities_index ON public.image_intensities USING btree (nw
CREATE UNIQUE INDEX image_sources_image_id_source_index ON public.image_sources USING btree (image_id, source); CREATE UNIQUE INDEX image_sources_image_id_source_index ON public.image_sources USING btree (image_id, source);
--
-- Name: image_tag_locks_image_id_tag_id_index; Type: INDEX; Schema: public; Owner: -
--
CREATE UNIQUE INDEX image_tag_locks_image_id_tag_id_index ON public.image_tag_locks USING btree (image_id, tag_id);
--
-- Name: image_tag_locks_tag_id_index; Type: INDEX; Schema: public; Owner: -
--
CREATE INDEX image_tag_locks_tag_id_index ON public.image_tag_locks USING btree (tag_id);
-- --
-- Name: index_adverts_on_restrictions; Type: INDEX; Schema: public; Owner: - -- Name: index_adverts_on_restrictions; Type: INDEX; Schema: public; Owner: -
-- --
@ -4772,6 +4796,22 @@ 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: image_tag_locks image_tag_locks_image_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.image_tag_locks
ADD CONSTRAINT image_tag_locks_image_id_fkey FOREIGN KEY (image_id) REFERENCES public.images(id) ON DELETE CASCADE;
--
-- Name: image_tag_locks image_tag_locks_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
ALTER TABLE ONLY public.image_tag_locks
ADD CONSTRAINT image_tag_locks_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE;
-- --
-- Name: user_tokens user_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - -- Name: user_tokens user_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
-- --
@ -4802,3 +4842,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20200817213256);
INSERT INTO public."schema_migrations" (version) VALUES (20200905214139); INSERT INTO public."schema_migrations" (version) VALUES (20200905214139);
INSERT INTO public."schema_migrations" (version) VALUES (20201124224116); INSERT INTO public."schema_migrations" (version) VALUES (20201124224116);
INSERT INTO public."schema_migrations" (version) VALUES (20210121200815); INSERT INTO public."schema_migrations" (version) VALUES (20210121200815);
INSERT INTO public."schema_migrations" (version) VALUES (20210301012137);