mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-17 11:04:22 +01:00
image hiding
This commit is contained in:
parent
756d2c91a6
commit
f9f6518d97
11 changed files with 270 additions and 12 deletions
|
@ -9,12 +9,14 @@ defmodule Philomena.Images do
|
|||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Images.Image
|
||||
alias Philomena.Images.Hider
|
||||
alias Philomena.Images.Uploader
|
||||
alias Philomena.SourceChanges.SourceChange
|
||||
alias Philomena.TagChanges.TagChange
|
||||
alias Philomena.Tags
|
||||
alias Philomena.Tags.Tag
|
||||
alias Philomena.Notifications
|
||||
alias Philomena.Interactions
|
||||
|
||||
@doc """
|
||||
Gets a single image.
|
||||
|
@ -199,6 +201,73 @@ defmodule Philomena.Images do
|
|||
}
|
||||
end
|
||||
|
||||
def hide_image(%Image{} = image, user, attrs) do
|
||||
Image.hide_changeset(image, attrs, user)
|
||||
|> internal_hide_image()
|
||||
end
|
||||
|
||||
def merge_image(%Image{} = image, duplicate_of_image) do
|
||||
result =
|
||||
Image.merge_changeset(image, duplicate_of_image)
|
||||
|> internal_hide_image()
|
||||
|
||||
case result do
|
||||
{:ok, _changes} ->
|
||||
Interactions.migrate_interactions(image, duplicate_of_image)
|
||||
result
|
||||
|
||||
_error ->
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
defp internal_hide_image(changeset) do
|
||||
Multi.new
|
||||
|> Multi.update(:image, changeset)
|
||||
|> Multi.run(:tags, fn repo, %{image: image} ->
|
||||
image = Repo.preload(image, :tags, force: true)
|
||||
|
||||
# I'm not convinced this is a good idea. It leads
|
||||
# to way too much drift, and the index has to be
|
||||
# maintained.
|
||||
tag_ids = Enum.map(image.tags, & &1.id)
|
||||
query = where(Tag, [t], t.id in ^tag_ids)
|
||||
|
||||
repo.update_all(query, inc: [images_count: -1])
|
||||
|
||||
{:ok, image.tags}
|
||||
end)
|
||||
|> Multi.run(:file, fn _repo, %{image: image} ->
|
||||
Hider.hide_thumbnails(image, image.hidden_image_key)
|
||||
|
||||
{:ok, nil}
|
||||
end)
|
||||
|> Repo.isolated_transaction(:serializable)
|
||||
end
|
||||
|
||||
def unhide_image(%Image{} = image) do
|
||||
key = image.hidden_image_key
|
||||
|
||||
Multi.new
|
||||
|> Multi.update(:image, Image.unhide_changeset(image))
|
||||
|> Multi.run(:tags, fn repo, %{image: image} ->
|
||||
image = Repo.preload(image, :tags, force: true)
|
||||
|
||||
tag_ids = Enum.map(image.tags, & &1.id)
|
||||
query = where(Tag, [t], t.id in ^tag_ids)
|
||||
|
||||
repo.update_all(query, inc: [images_count: 1])
|
||||
|
||||
{:ok, image.tags}
|
||||
end)
|
||||
|> Multi.run(:file, fn _repo, %{image: image} ->
|
||||
Hider.unhide_thumbnails(image, key)
|
||||
|
||||
{:ok, nil}
|
||||
end)
|
||||
|> Repo.isolated_transaction(:serializable)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deletes a Image.
|
||||
|
||||
|
|
34
lib/philomena/images/hider.ex
Normal file
34
lib/philomena/images/hider.ex
Normal file
|
@ -0,0 +1,34 @@
|
|||
defmodule Philomena.Images.Hider do
|
||||
@moduledoc """
|
||||
Hiding logic for images.
|
||||
"""
|
||||
|
||||
alias Philomena.Images.Image
|
||||
|
||||
def hide_thumbnails(image, key) do
|
||||
source = image_thumb_dir(image)
|
||||
target = image_thumb_dir(image, key)
|
||||
|
||||
File.rename!(source, target)
|
||||
end
|
||||
|
||||
def unhide_thumbnails(image, key) do
|
||||
source = image_thumb_dir(image, key)
|
||||
target = image_thumb_dir(image)
|
||||
|
||||
File.rename!(source, target)
|
||||
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)])
|
||||
|
||||
defp image_thumb_dir(%Image{created_at: created_at, id: id}, key),
|
||||
do: Path.join([image_thumbnail_root(), time_identifier(created_at), to_string(id) <> "-" <> key])
|
||||
|
||||
defp time_identifier(time),
|
||||
do: Enum.join([time.year, time.month, time.day], "/")
|
||||
|
||||
defp image_thumbnail_root,
|
||||
do: Application.get_env(:philomena, :image_file_root) <> "/thumbs"
|
||||
end
|
|
@ -179,6 +179,30 @@ defmodule Philomena.Images.Image do
|
|||
|> validate_length(:description, max: 50_000, count: :bytes)
|
||||
end
|
||||
|
||||
def hide_changeset(image, attrs, user) do
|
||||
image
|
||||
|> cast(attrs, [:deletion_reason])
|
||||
|> put_change(:deleter_id, user.id)
|
||||
|> put_change(:hidden_image_key, create_key())
|
||||
|> put_change(:hidden_from_users, true)
|
||||
|> validate_required([:deletion_reason, :deleter_id])
|
||||
end
|
||||
|
||||
def merge_changeset(image, duplicate_of_image) do
|
||||
change(image)
|
||||
|> put_change(:duplicate_id, duplicate_of_image.id)
|
||||
|> put_change(:hidden_image_key, create_key())
|
||||
|> put_change(:hidden_from_users, true)
|
||||
end
|
||||
|
||||
def unhide_changeset(image) do
|
||||
change(image)
|
||||
|> put_change(:deleter_id, nil)
|
||||
|> put_change(:hidden_image_key, nil)
|
||||
|> put_change(:hidden_from_users, false)
|
||||
|> put_change(:deletion_reason, nil)
|
||||
end
|
||||
|
||||
def cache_changeset(image) do
|
||||
changeset = change(image)
|
||||
image = apply_changes(changeset)
|
||||
|
@ -225,4 +249,8 @@ defmodule Philomena.Images.Image do
|
|||
|
||||
{tag_list_cache, tag_list_plus_alias_cache, file_name_cache}
|
||||
end
|
||||
|
||||
defp create_key do
|
||||
Base.encode16(:crypto.strong_rand_bytes(6), case: :lower)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,9 @@ defmodule Philomena.Interactions do
|
|||
alias Philomena.ImageHides.ImageHide
|
||||
alias Philomena.ImageFaves.ImageFave
|
||||
alias Philomena.ImageVotes.ImageVote
|
||||
alias Philomena.Images.Image
|
||||
alias Philomena.Repo
|
||||
alias Ecto.Multi
|
||||
|
||||
def user_interactions(_images, nil),
|
||||
do: []
|
||||
|
@ -53,6 +55,46 @@ defmodule Philomena.Interactions do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
def migrate_interactions(source, target) do
|
||||
now = DateTime.utc_now()
|
||||
source = Repo.preload(source, [:hiders, :favers, :upvoters, :downvoters])
|
||||
|
||||
new_hides = Enum.map(source.hiders, &%{image_id: target.id, user_id: &1.id, created_at: now})
|
||||
new_faves = Enum.map(source.favers, &%{image_id: target.id, user_id: &1.id, created_at: now})
|
||||
new_upvotes = Enum.map(source.upvoters, &%{image_id: target.id, user_id: &1.id, created_at: now, up: true})
|
||||
new_downvotes = Enum.map(source.downvoters, &%{image_id: target.id, user_id: &1.id, created_at: now, up: false})
|
||||
|
||||
Multi.new
|
||||
|> Multi.run(:hides, fn repo, %{} ->
|
||||
{count, nil} = repo.insert_all(ImageHide, new_hides, on_conflict: :nothing)
|
||||
|
||||
{:ok, count}
|
||||
end)
|
||||
|> Multi.run(:faves, fn repo, %{} ->
|
||||
{count, nil} = repo.insert_all(ImageFave, new_faves, on_conflict: :nothing)
|
||||
|
||||
{:ok, count}
|
||||
end)
|
||||
|> Multi.run(:upvotes, fn repo, %{} ->
|
||||
{count, nil} = repo.insert_all(ImageVote, new_upvotes, on_conflict: :nothing)
|
||||
|
||||
{:ok, count}
|
||||
end)
|
||||
|> Multi.run(:downvotes, fn repo, %{} ->
|
||||
{count, nil} = repo.insert_all(ImageVote, new_downvotes, on_conflict: :nothing)
|
||||
|
||||
{:ok, count}
|
||||
end)
|
||||
|> Multi.run(:image, fn repo, %{hides: hides, faves: faves, upvotes: upvotes, downvotes: downvotes} ->
|
||||
image_query = where(Image, id: ^target.id)
|
||||
|
||||
repo.update_all(image_query, inc: [hides: hides, faves: faves, upvotes: upvotes, downvotes: downvotes, score: upvotes - downvotes])
|
||||
|
||||
{:ok, nil}
|
||||
end)
|
||||
|> Repo.isolated_transaction(:serializable)
|
||||
end
|
||||
|
||||
defp union_all_queries([query]),
|
||||
do: query
|
||||
defp union_all_queries([query | rest]),
|
||||
|
|
43
lib/philomena_web/controllers/image/delete_controller.ex
Normal file
43
lib/philomena_web/controllers/image/delete_controller.ex
Normal file
|
@ -0,0 +1,43 @@
|
|||
defmodule PhilomenaWeb.Image.DeleteController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
# N.B.: this would be Image.Hide, because it hides the image, but that is
|
||||
# taken by the user action
|
||||
|
||||
alias Philomena.Images.Image
|
||||
alias Philomena.Images
|
||||
alias Philomena.Tags
|
||||
|
||||
plug PhilomenaWeb.CanaryMapPlug, create: :hide, delete: :hide
|
||||
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
||||
|
||||
def create(conn, %{"image" => image_params}) do
|
||||
image = conn.assigns.image
|
||||
user = conn.assigns.current_user
|
||||
|
||||
case Images.hide_image(image, user, image_params) do
|
||||
{:ok, %{image: image, tags: tags}} ->
|
||||
Images.reindex_image(image)
|
||||
Tags.reindex_tags(tags)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Image successfully hidden.")
|
||||
|> redirect(to: Routes.image_path(conn, :show, image))
|
||||
|
||||
{:error, :image, changeset, _changes} ->
|
||||
render(conn, "new.html", image: image, changeset: changeset)
|
||||
end
|
||||
end
|
||||
|
||||
def delete(conn, _params) do
|
||||
image = conn.assigns.image
|
||||
|
||||
{:ok, %{image: image, tags: tags}} = Images.unhide_image(image)
|
||||
Images.reindex_image(image)
|
||||
Tags.reindex_tags(tags)
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Image successfully unhidden.")
|
||||
|> redirect(to: Routes.image_path(conn, :show, image))
|
||||
end
|
||||
end
|
|
@ -73,9 +73,7 @@ defmodule PhilomenaWeb.ImageController do
|
|||
|
||||
{user_galleries, image_galleries} = image_and_user_galleries(image, conn.assigns.current_user)
|
||||
|
||||
render(
|
||||
conn,
|
||||
"show.html",
|
||||
assigns = [
|
||||
image: image,
|
||||
comments: comments,
|
||||
image_changeset: image_changeset,
|
||||
|
@ -86,7 +84,13 @@ defmodule PhilomenaWeb.ImageController do
|
|||
interactions: interactions,
|
||||
watching: watching,
|
||||
layout_class: "layout--wide"
|
||||
)
|
||||
]
|
||||
|
||||
if image.hidden_from_users do
|
||||
render(conn, "deleted.html", assigns)
|
||||
else
|
||||
render(conn, "show.html", assigns)
|
||||
end
|
||||
end
|
||||
|
||||
def new(conn, _params) do
|
||||
|
@ -157,9 +161,6 @@ defmodule PhilomenaWeb.ImageController do
|
|||
|> put_flash(:info, "The image you were looking for has been marked a duplicate of the image below")
|
||||
|> redirect(to: Routes.image_path(conn, :show, image.duplicate_id))
|
||||
|
||||
image.hidden_from_users ->
|
||||
render(conn, "deleted.html", image: image)
|
||||
|
||||
true ->
|
||||
conn
|
||||
|> assign(:image, image)
|
||||
|
|
|
@ -105,6 +105,7 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/subscription", Image.SubscriptionController, only: [:create, :delete], singleton: true
|
||||
resources "/read", Image.ReadController, only: [:create], singleton: true
|
||||
resources "/comments", Image.CommentController, only: [:edit, :update]
|
||||
resources "/delete", Image.DeleteController, only: [:create, :delete], singleton: true
|
||||
end
|
||||
|
||||
resources "/forums", ForumController, only: [] do
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
=< link("your current filter", to: Routes.filter_path(@conn, :show, @conn.assigns.current_filter), class: "filter-link")
|
||||
' .
|
||||
|
||||
#image_target.hidden.image-show data-scaled="true" data-uris=Jason.encode!(thumb_urls(@image, false)) data-width=@image.image_width data-height=@image.image_height
|
||||
#image_target.hidden.image-show data-scaled="true" data-uris=Jason.encode!(thumb_urls(@image, can?(@conn, :hide, @image))) data-width=@image.image_width data-height=@image.image_height
|
||||
= if @image.image_mime_type == "video/webm" do
|
||||
video controls=true
|
||||
- else
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
- display_mod_tools? = can?(@conn, :hide, @image)
|
||||
|
||||
#image_options_area
|
||||
.block__header.block__header--js-tabbed
|
||||
a href="#" data-click-tab="reporting" data-load-tab=Routes.image_reporting_path(@conn, :show, @image)
|
||||
|
@ -9,6 +11,16 @@
|
|||
a href="#" data-click-tab="favoriters" data-load-tab=Routes.image_favorites_path(@conn, :index, @image)
|
||||
i.fa.fa-star>
|
||||
| List favoriters
|
||||
= if display_mod_tools? do
|
||||
a href="#" data-click-tab="replace"
|
||||
i.fa.fa-upload>
|
||||
| Replace
|
||||
a href="#" data-click-tab="administration"
|
||||
i.fa.fa-toolbox>
|
||||
| Administrate
|
||||
= if present?(@image.scratchpad) do
|
||||
i.fa.fa-sticky-note.fa--important<
|
||||
i.fa.fa-exclamation.fa--important
|
||||
|
||||
.block__tab.hidden data-tab="favoriters"
|
||||
p Loading...
|
||||
|
@ -52,4 +64,29 @@
|
|||
| Copy
|
||||
br
|
||||
textarea.input.input--wide.input--separate-top#bbcode_embed_thumbnail_tag rows="2" cols="100" readonly="readonly"
|
||||
= "[img]#{thumb_url(@image, false, :medium)}[/img]\n[url=#{Routes.image_url(@conn, :show, @image)}]View on Derpibooru[/url]#{source_link}"
|
||||
= "[img]#{thumb_url(@image, false, :medium)}[/img]\n[url=#{Routes.image_url(@conn, :show, @image)}]View on Derpibooru[/url]#{source_link}"
|
||||
= if display_mod_tools? do
|
||||
.block__tab.hidden data-tab="replace"
|
||||
/= form_tag image_file_path(@image), method: :put, multipart: true do
|
||||
= render partial: 'layouts/image_upload', locals: { form: nil, field: :image }
|
||||
= submit_tag 'Save changes', class: 'button', autocomplete: 'off', data: { disable_with: 'Replacing...' }
|
||||
|
||||
.block__tab.hidden data-tab="administration"
|
||||
.block.block--danger
|
||||
a.button.button--link> href="#"
|
||||
i.far.fa-edit
|
||||
= if present?(@image.scratchpad) do
|
||||
strong> Mod notes:
|
||||
= escape_nl2br @image.scratchpad
|
||||
- else
|
||||
em No mod notes present
|
||||
|
||||
= if not @image.hidden_from_users do
|
||||
= form_for @changeset, Routes.image_delete_path(@conn, :create, @image), [method: "post"], fn f ->
|
||||
= label f, :deletion_reason, "Deletion reason (cannot be empty)"
|
||||
.field.field--inline
|
||||
= text_input f, :deletion_reason, class: "input input--wide", placeholder: "Rule violation", required: true
|
||||
= submit "Delete", class: "button button--state-danger button--separate-left"
|
||||
|
||||
- else
|
||||
= button_to "Restore", Routes.image_delete_path(@conn, :delete, @image), method: "delete", class: "button button--state-success"
|
|
@ -6,7 +6,7 @@
|
|||
strong
|
||||
= @image.deletion_reason || "Unknown (likely deleted in error). Please contact a moderator."
|
||||
|
||||
= if can?(@conn, :undelete, @image) do
|
||||
= if can?(@conn, :hide, @image) do
|
||||
p
|
||||
strong> Spoilers!
|
||||
' Done by:
|
||||
|
@ -20,4 +20,7 @@
|
|||
=> link "tagging guidelines", to: "/pages/tags"
|
||||
' and
|
||||
= link "rules of the site", to: "/pages/rules"
|
||||
' . Other useful links can be found at the bottom of the page.
|
||||
' . Other useful links can be found at the bottom of the page.
|
||||
|
||||
= if can?(@conn, :hide, @image) do
|
||||
= render PhilomenaWeb.ImageView, "show.html", assigns
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
= render PhilomenaWeb.ImageView, "_tags.html", image: @image, tag_change_count: @tag_change_count, changeset: @image_changeset, conn: @conn
|
||||
= render PhilomenaWeb.ImageView, "_source.html", image: @image, source_change_count: @source_change_count, changeset: @image_changeset, conn: @conn
|
||||
= render PhilomenaWeb.ImageView, "_options.html", image: @image, conn: @conn
|
||||
= render PhilomenaWeb.ImageView, "_options.html", image: @image, changeset: @image_changeset, conn: @conn
|
||||
|
||||
h4 Comments
|
||||
= cond do
|
||||
|
|
Loading…
Reference in a new issue