add irreversible admin-only action to permanently delete an image file

This commit is contained in:
byte[] 2020-08-05 14:23:11 -04:00
parent 90a16b3317
commit 3ba38edf0b
7 changed files with 74 additions and 0 deletions

View file

@ -138,6 +138,20 @@ defmodule Philomena.Images do
|> Repo.isolated_transaction(:serializable) |> Repo.isolated_transaction(:serializable)
end end
def destroy_image(%Image{} = image) do
changeset = Image.remove_image_changeset(image)
Multi.new()
|> Multi.update(:image, changeset)
|> Multi.run(:remove_file, fn _repo, %{image: image} ->
Uploader.unpersist_old_upload(image)
Hider.destroy_thumbnails(image)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end
def lock_comments(%Image{} = image, locked) do def lock_comments(%Image{} = image, locked) do
image image
|> Image.lock_comments_changeset(locked) |> Image.lock_comments_changeset(locked)

View file

@ -21,6 +21,14 @@ defmodule Philomena.Images.Hider do
File.rename(source, target) File.rename(source, target)
end end
def destroy_thumbnails(image) do
hidden = image_thumb_dir(image, image.hidden_image_key)
normal = image_thumb_dir(image)
File.rm_rf(hidden)
File.rm_rf(normal)
end
# fixme: these are copied from the thumbnailer # fixme: these are copied from the thumbnailer
defp image_thumb_dir(%Image{created_at: created_at, id: id}), defp image_thumb_dir(%Image{created_at: created_at, id: id}),
do: Path.join([image_thumbnail_root(), time_identifier(created_at), to_string(id)]) do: Path.join([image_thumbnail_root(), time_identifier(created_at), to_string(id)])

View file

@ -166,6 +166,12 @@ defmodule Philomena.Images.Image do
|> unsafe_validate_unique([:image_orig_sha512_hash], Repo) |> unsafe_validate_unique([:image_orig_sha512_hash], Repo)
end end
def remove_image_changeset(image) do
image
|> change(removed_image: image.image)
|> change(image: nil)
end
def source_changeset(image, attrs) do def source_changeset(image, attrs) do
image image
|> cast(attrs, [:source_url]) |> cast(attrs, [:source_url])

View file

@ -45,6 +45,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "moderator"}, :show, %Filter{}), do: true def can?(%User{role: "moderator"}, :show, %Filter{}), do: true
# Manage images # Manage images
def can?(%User{role: "moderator"}, :destroy, %Image{}), do: false
def can?(%User{role: "moderator"}, _action, Image), do: true def can?(%User{role: "moderator"}, _action, Image), do: true
def can?(%User{role: "moderator"}, _action, %Image{}), do: true def can?(%User{role: "moderator"}, _action, %Image{}), do: true

View file

@ -0,0 +1,39 @@
defmodule PhilomenaWeb.Image.DestroyController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :destroy
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :verify_deleted when action in [:create]
def create(conn, _params) do
image = conn.assigns.image
case Images.destroy_image(image) do
{:ok, %{image: image}} ->
conn
|> put_flash(:info, "Image contents destroyed.")
|> redirect(to: Routes.image_path(conn, :show, image))
_error ->
conn
|> put_flash(:error, "Failed to destroy image.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end
defp verify_deleted(conn, _opts) do
case conn.assigns.image.hidden_from_users do
true ->
conn
_false ->
conn
|> put_flash(:error, "Cannot destroy a non-hidden image!")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
|> halt()
end
end
end

View file

@ -201,6 +201,7 @@ defmodule PhilomenaWeb.Router do
resources "/scratchpad", Image.ScratchpadController, only: [:edit, :update], singleton: true resources "/scratchpad", Image.ScratchpadController, only: [:edit, :update], singleton: true
resources "/uploader", Image.UploaderController, only: [:update], singleton: true resources "/uploader", Image.UploaderController, only: [:update], singleton: true
resources "/anonymous", Image.AnonymousController, only: [:create, :delete], singleton: true resources "/anonymous", Image.AnonymousController, only: [:create, :delete], singleton: true
resources "/destroy", Image.DestroyController, only: [:create], singleton: true
resources "/comment_lock", Image.CommentLockController, resources "/comment_lock", Image.CommentLockController,
only: [:create, :delete], only: [:create, :delete],

View file

@ -141,3 +141,8 @@
= button_to "Lock tag editing", Routes.image_tag_lock_path(@conn, :create, @image), method: "post", class: "button" = button_to "Lock tag editing", Routes.image_tag_lock_path(@conn, :create, @image), method: "post", class: "button"
- 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
.flex.flex--spaced-out
= 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?"]