mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-01-19 14:17:59 +01:00
tag change mass reversion
This commit is contained in:
parent
563172f283
commit
81b5a58fab
5 changed files with 175 additions and 49 deletions
|
@ -7,18 +7,90 @@ defmodule Philomena.TagChanges do
|
|||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.TagChanges.TagChange
|
||||
alias Philomena.Images.Tagging
|
||||
alias Philomena.Tags.Tag
|
||||
alias Philomena.Images
|
||||
|
||||
@doc """
|
||||
Returns the list of tag_changes.
|
||||
# TODO: this is substantially similar to Images.batch_update/4.
|
||||
# Perhaps it should be extracted.
|
||||
def mass_revert(ids, attributes) do
|
||||
now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
|
||||
tag_change_attributes = Map.merge(attributes, %{created_at: now, updated_at: now})
|
||||
tag_attributes = %{name: "", slug: "", created_at: now, updated_at: now}
|
||||
|
||||
## Examples
|
||||
tag_changes =
|
||||
TagChange
|
||||
|> join(:inner, [tc], _ in assoc(tc, :image))
|
||||
|> where([tc, i], tc.id in ^ids and i.hidden_from_users == false)
|
||||
|> order_by(desc: :created_at)
|
||||
|> Repo.all()
|
||||
|> Enum.reject(&is_nil(&1.tag_id))
|
||||
|> Enum.uniq_by(&{&1.image_id, &1.tag_id})
|
||||
|
||||
iex> list_tag_changes()
|
||||
[%TagChange{}, ...]
|
||||
{added, removed} = Enum.split_with(tag_changes, & &1.added)
|
||||
|
||||
"""
|
||||
def list_tag_changes do
|
||||
Repo.all(TagChange)
|
||||
image_ids =
|
||||
tag_changes
|
||||
|> Enum.map(& &1.image_id)
|
||||
|> Enum.uniq()
|
||||
|
||||
to_remove =
|
||||
added
|
||||
|> Enum.map(&{&1.image_id, &1.tag_id})
|
||||
|> Enum.reduce(Tagging, fn {image_id, tag_id}, q ->
|
||||
or_where(q, image_id: ^image_id, tag_id: ^tag_id)
|
||||
end)
|
||||
|> select([t], [t.image_id, t.tag_id])
|
||||
|
||||
to_add =
|
||||
Enum.map(removed, &%{image_id: &1.image_id, tag_id: &1.tag_id})
|
||||
|
||||
Repo.transaction(fn ->
|
||||
{_count, inserted} = Repo.insert_all(Tagging, to_add, on_conflict: :nothing, returning: [:image_id, :tag_id])
|
||||
{_count, deleted} = Repo.delete_all(to_remove)
|
||||
|
||||
inserted = Enum.map(inserted, &[&1.image_id, &1.tag_id])
|
||||
|
||||
added_changes = Enum.map(inserted, fn [image_id, tag_id] ->
|
||||
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: true})
|
||||
end)
|
||||
|
||||
removed_changes = Enum.map(deleted, fn [image_id, tag_id] ->
|
||||
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: false})
|
||||
end)
|
||||
|
||||
Repo.insert_all(TagChange, added_changes ++ removed_changes)
|
||||
|
||||
# In order to merge into the existing tables here in one go, insert_all
|
||||
# is used with a query that is guaranteed to conflict on every row by
|
||||
# using the primary key.
|
||||
|
||||
added_upserts =
|
||||
inserted
|
||||
|> Enum.group_by(fn [_image_id, tag_id] -> tag_id end)
|
||||
|> Enum.map(fn {tag_id, instances} -> Map.merge(tag_attributes, %{id: tag_id, images_count: length(instances)}) end)
|
||||
|
||||
removed_upserts =
|
||||
deleted
|
||||
|> Enum.group_by(fn [_image_id, tag_id] -> tag_id end)
|
||||
|> Enum.map(fn {tag_id, instances} -> Map.merge(tag_attributes, %{id: tag_id, images_count: -length(instances)}) end)
|
||||
|
||||
update_query =
|
||||
update(Tag, inc: [images_count: fragment("EXCLUDED.images_count")])
|
||||
|
||||
upserts = added_upserts ++ removed_upserts
|
||||
|
||||
Repo.insert_all(Tag, upserts, on_conflict: update_query, conflict_target: [:id])
|
||||
end)
|
||||
|> case do
|
||||
{:ok, _result} ->
|
||||
Images.reindex_images(image_ids)
|
||||
|
||||
{:ok, tag_changes}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
defmodule PhilomenaWeb.TagChange.RevertController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.TagChanges.TagChange
|
||||
alias Philomena.TagChanges
|
||||
|
||||
plug :verify_authorized
|
||||
plug PhilomenaWeb.UserAttributionPlug
|
||||
|
||||
def create(conn, %{"ids" => ids}) when is_list(ids) do
|
||||
attributes = conn.assigns.attributes
|
||||
attributes = %{
|
||||
ip: attributes[:ip],
|
||||
fingerprint: attributes[:fingerprint],
|
||||
referrer: attributes[:referrer],
|
||||
user_agent: attributes[:referrer],
|
||||
user_id: attributes[:user].id
|
||||
}
|
||||
|
||||
case TagChanges.mass_revert(ids, attributes) do
|
||||
{:ok, tag_changes} ->
|
||||
conn
|
||||
|> put_flash(:info, "Successfully reverted #{length(tag_changes)} tag changes.")
|
||||
|> redirect(external: conn.assigns.referrer)
|
||||
|
||||
_error ->
|
||||
conn
|
||||
|> put_flash(:error, "Couldn't revert those tag changes!")
|
||||
|> redirect(external: conn.assigns.referrer)
|
||||
end
|
||||
end
|
||||
|
||||
defp verify_authorized(conn, _params) do
|
||||
case Canada.Can.can?(conn.assigns.current_user, :revert, TagChange) do
|
||||
true -> conn
|
||||
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -260,6 +260,8 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/reindex", Tag.ReindexController, only: [:create], singleton: true
|
||||
end
|
||||
|
||||
resources "/tag_changes/revert", TagChange.RevertController, as: :tag_change_revert, only: [:create], singleton: true
|
||||
|
||||
resources "/pages", PageController, only: [:index, :new, :create, :edit, :update]
|
||||
resources "/channels", ChannelController, only: [:new, :create, :edit, :update]
|
||||
end
|
||||
|
|
|
@ -1,52 +1,62 @@
|
|||
.block__header
|
||||
= @pagination
|
||||
|
||||
.block__content
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
th colspan=2 Image
|
||||
th Tag
|
||||
th Action
|
||||
th Timestamp
|
||||
th User
|
||||
|
||||
tbody
|
||||
= for tag_change <- @tag_changes do
|
||||
= form_for :tag_changes, Routes.tag_change_revert_path(@conn, :create), fn _f ->
|
||||
.block__content
|
||||
table.table
|
||||
thead
|
||||
tr
|
||||
td.center
|
||||
= link tag_change.image_id, to: Routes.image_path(@conn, :show, tag_change.image)
|
||||
td.center
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: tag_change.image, size: :thumb_tiny, conn: @conn
|
||||
= if reverts_tag_changes?(@conn) do
|
||||
th Revert?
|
||||
th colspan=2 Image
|
||||
th Tag
|
||||
th Action
|
||||
th Timestamp
|
||||
th User
|
||||
|
||||
td
|
||||
= if tag_change.tag do
|
||||
= render PhilomenaWeb.TagView, "_tag.html", tag: tag_change.tag, conn: @conn
|
||||
tbody
|
||||
= for tag_change <- @tag_changes do
|
||||
tr
|
||||
= if reverts_tag_changes?(@conn) do
|
||||
td.center
|
||||
input type="checkbox" name="ids[]" value=tag_change.id
|
||||
|
||||
td.center
|
||||
= link tag_change.image_id, to: Routes.image_path(@conn, :show, tag_change.image)
|
||||
td.center
|
||||
= render PhilomenaWeb.ImageView, "_image_container.html", image: tag_change.image, size: :thumb_tiny, conn: @conn
|
||||
|
||||
td
|
||||
= if tag_change.tag do
|
||||
= render PhilomenaWeb.TagView, "_tag.html", tag: tag_change.tag, conn: @conn
|
||||
- else
|
||||
= tag_change.tag_name_cache || "Unknown tag"
|
||||
|
||||
= if tag_change.added do
|
||||
td.success Added
|
||||
- else
|
||||
= tag_change.tag_name_cache || "Unknown tag"
|
||||
|
||||
= if tag_change.added do
|
||||
td.success Added
|
||||
- else
|
||||
td.danger Removed
|
||||
|
||||
td
|
||||
= pretty_time(tag_change.created_at)
|
||||
td.danger Removed
|
||||
|
||||
td class=user_column_class(tag_change)
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: tag_change, conn: @conn
|
||||
td
|
||||
= pretty_time(tag_change.created_at)
|
||||
|
||||
= if can?(@conn, :show, :ip_address) do
|
||||
=> link_to_ip @conn, tag_change.ip
|
||||
=> link_to_fingerprint @conn, tag_change.fingerprint
|
||||
td class=user_column_class(tag_change)
|
||||
=> render PhilomenaWeb.UserAttributionView, "_anon_user.html", object: tag_change, conn: @conn
|
||||
|
||||
= if staff?(tag_change) do
|
||||
br
|
||||
small
|
||||
strong> Stop!
|
||||
' This user is a staff member.
|
||||
= if can?(@conn, :show, :ip_address) do
|
||||
=> link_to_ip @conn, tag_change.ip
|
||||
=> link_to_fingerprint @conn, tag_change.fingerprint
|
||||
|
||||
= if staff?(tag_change) do
|
||||
br
|
||||
' Ask them before reverting their changes.
|
||||
small
|
||||
strong> Stop!
|
||||
' This user is a staff member.
|
||||
br
|
||||
' Ask them before reverting their changes.
|
||||
|
||||
.block__header
|
||||
= @pagination
|
||||
.block__header
|
||||
= @pagination
|
||||
|
||||
= if reverts_tag_changes?(@conn) do
|
||||
= submit "Revert selected", class: "button", data: [confirm: "Are you really, really sure?"]
|
||||
|
|
|
@ -10,4 +10,7 @@ defmodule PhilomenaWeb.TagChangeView do
|
|||
false -> nil
|
||||
end
|
||||
end
|
||||
|
||||
def reverts_tag_changes?(conn),
|
||||
do: can?(conn, :revert, Philomena.TagChanges.TagChange)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue