diff --git a/lib/philomena/images.ex b/lib/philomena/images.ex index 403608fb..051ad152 100644 --- a/lib/philomena/images.ex +++ b/lib/philomena/images.ex @@ -138,6 +138,20 @@ defmodule Philomena.Images do |> Repo.isolated_transaction(:serializable) 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 image |> Image.lock_comments_changeset(locked) diff --git a/lib/philomena/images/hider.ex b/lib/philomena/images/hider.ex index 77aabb10..78fbf203 100644 --- a/lib/philomena/images/hider.ex +++ b/lib/philomena/images/hider.ex @@ -21,6 +21,14 @@ defmodule Philomena.Images.Hider do File.rename(source, target) 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 defp image_thumb_dir(%Image{created_at: created_at, id: id}), do: Path.join([image_thumbnail_root(), time_identifier(created_at), to_string(id)]) diff --git a/lib/philomena/images/image.ex b/lib/philomena/images/image.ex index fae448bc..25b2ff2c 100644 --- a/lib/philomena/images/image.ex +++ b/lib/philomena/images/image.ex @@ -166,6 +166,12 @@ defmodule Philomena.Images.Image do |> unsafe_validate_unique([:image_orig_sha512_hash], Repo) end + def remove_image_changeset(image) do + image + |> change(removed_image: image.image) + |> change(image: nil) + end + def source_changeset(image, attrs) do image |> cast(attrs, [:source_url]) diff --git a/lib/philomena/users/ability.ex b/lib/philomena/users/ability.ex index 9f3937a7..2fdb17f3 100644 --- a/lib/philomena/users/ability.ex +++ b/lib/philomena/users/ability.ex @@ -45,6 +45,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do def can?(%User{role: "moderator"}, :show, %Filter{}), do: true # 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 diff --git a/lib/philomena_web/controllers/image/destroy_controller.ex b/lib/philomena_web/controllers/image/destroy_controller.ex new file mode 100644 index 00000000..f2557660 --- /dev/null +++ b/lib/philomena_web/controllers/image/destroy_controller.ex @@ -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 diff --git a/lib/philomena_web/router.ex b/lib/philomena_web/router.ex index 1c0518f3..def1eea2 100644 --- a/lib/philomena_web/router.ex +++ b/lib/philomena_web/router.ex @@ -201,6 +201,7 @@ defmodule PhilomenaWeb.Router do resources "/scratchpad", Image.ScratchpadController, only: [:edit, :update], singleton: true resources "/uploader", Image.UploaderController, only: [:update], singleton: true resources "/anonymous", Image.AnonymousController, only: [:create, :delete], singleton: true + resources "/destroy", Image.DestroyController, only: [:create], singleton: true resources "/comment_lock", Image.CommentLockController, only: [:create, :delete], diff --git a/lib/philomena_web/templates/image/_options.html.slime b/lib/philomena_web/templates/image/_options.html.slime index 727fbcf0..83ad01b1 100644 --- a/lib/philomena_web/templates/image/_options.html.slime +++ b/lib/philomena_web/templates/image/_options.html.slime @@ -141,3 +141,8 @@ = button_to "Lock tag editing", Routes.image_tag_lock_path(@conn, :create, @image), method: "post", class: "button" - else = 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?"]