image mod tools

This commit is contained in:
byte[] 2019-12-16 00:25:06 -05:00
parent 6c3dd03270
commit a74e7e9170
16 changed files with 420 additions and 5 deletions

View file

@ -11,6 +11,7 @@ defmodule Philomena.Images do
alias Philomena.Images.Image
alias Philomena.Images.Hider
alias Philomena.Images.Uploader
alias Philomena.ImageFeatures.ImageFeature
alias Philomena.SourceChanges.SourceChange
alias Philomena.TagChanges.TagChange
alias Philomena.Tags
@ -84,6 +85,91 @@ defmodule Philomena.Images do
|> Repo.isolated_transaction(:serializable)
end
def feature_image(featurer, %Image{} = image) do
image = Repo.preload(image, :tags)
[featured] = Tags.get_or_create_tags("featured image")
feature =
%ImageFeature{user_id: featurer.id, image_id: image.id}
|> ImageFeature.changeset(%{})
image =
image
|> Image.tag_changeset(%{}, image.tags, [featured | image.tags])
|> Image.cache_changeset()
Multi.new()
|> Multi.insert(:feature, feature)
|> Multi.update(:image, image)
|> Multi.run(:added_tag_count, fn repo, %{image: image} ->
tag_ids = image.added_tags |> Enum.map(& &1.id)
tags = Tag |> where([t], t.id in ^tag_ids)
{count, nil} = repo.update_all(tags, inc: [images_count: 1])
{:ok, count}
end)
|> Repo.isolated_transaction(:serializable)
end
def lock_comments(%Image{} = image, locked) do
image
|> Image.lock_comments_changeset(locked)
|> Repo.update()
end
def lock_description(%Image{} = image, locked) do
image
|> Image.lock_description_changeset(locked)
|> Repo.update()
end
def lock_tags(%Image{} = image, locked) do
image
|> Image.lock_tags_changeset(locked)
|> Repo.update()
end
def remove_hash(%Image{} = image) do
image
|> Image.remove_hash_changeset()
|> Repo.update()
end
def update_scratchpad(%Image{} = image, attrs) do
image
|> Image.scratchpad_changeset(attrs)
|> Repo.update()
end
def remove_source_history(%Image{} = image) do
image
|> Repo.preload(:source_changes)
|> Image.remove_source_history_changeset()
|> Repo.update()
end
def repair_image(%Image{} = image) do
Philomena.Images.Thumbnailer.generate_thumbnails(image.id)
end
def update_file(%Image{} = image, attrs) do
image =
image
|> Image.changeset(attrs)
|> Uploader.analyze_upload(attrs)
Multi.new()
|> Multi.update(:image, image)
|> Multi.run(:after, fn _repo, %{image: image} ->
Uploader.persist_upload(image)
Uploader.unpersist_old_upload(image)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end
@doc """
Updates a image.

View file

@ -35,7 +35,7 @@ defmodule Philomena.Images.Image do
has_many :hides, ImageHide
has_many :gallery_interactions, Galleries.Interaction
has_many :subscriptions, Subscription
has_many :source_changes, SourceChange
has_many :source_changes, SourceChange, on_replace: :delete
has_many :tag_changes, TagChange
has_many :upvoters, through: [:upvotes, :user]
has_many :downvoters, through: [:downvotes, :user]
@ -204,6 +204,32 @@ defmodule Philomena.Images.Image do
|> put_change(:duplicate_id, nil)
end
def lock_comments_changeset(image, locked) do
change(image, commenting_allowed: not locked)
end
def lock_description_changeset(image, locked) do
change(image, description_editing_allowed: not locked)
end
def lock_tags_changeset(image, locked) do
change(image, tag_editing_allowed: not locked)
end
def remove_hash_changeset(image) do
change(image, image_orig_sha512_hash: nil)
end
def scratchpad_changeset(image, attrs) do
cast(image, attrs, [:scratchpad])
end
def remove_source_history_changeset(image) do
change(image)
|> put_change(:source_url, nil)
|> put_assoc(:source_changes, [])
end
def cache_changeset(image) do
changeset = change(image)
image = apply_changes(changeset)

View file

@ -0,0 +1,25 @@
defmodule PhilomenaWeb.Image.CommentLockController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :hide, delete: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def create(conn, _params) do
{:ok, image} = Images.lock_comments(conn.assigns.image, true)
conn
|> put_flash(:info, "Successfully locked comments.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
def delete(conn, _params) do
{:ok, image} = Images.lock_comments(conn.assigns.image, false)
conn
|> put_flash(:info, "Successfully unlocked comments.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -0,0 +1,25 @@
defmodule PhilomenaWeb.Image.DescriptionLockController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :hide, delete: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def create(conn, _params) do
{:ok, image} = Images.lock_description(conn.assigns.image, true)
conn
|> put_flash(:info, "Successfully locked description.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
def delete(conn, _params) do
{:ok, image} = Images.lock_description(conn.assigns.image, false)
conn
|> put_flash(:info, "Successfully unlocked description.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -0,0 +1,32 @@
defmodule PhilomenaWeb.Image.FeatureController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
alias Philomena.Tags
plug PhilomenaWeb.CanaryMapPlug, create: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :verify_not_deleted
def create(conn, _params) do
{:ok, %{image: image}} = Images.feature_image(conn.assigns.current_user, conn.assigns.image)
Tags.reindex_tags(image.added_tags)
conn
|> put_flash(:info, "Image marked as featured image.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
defp verify_not_deleted(conn, _opts) do
case conn.assigns.image.hidden_from_users do
true ->
conn
|> put_flash(:error, "Cannot feature a hidden image.")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
_false ->
conn
end
end
end

View file

@ -0,0 +1,40 @@
defmodule PhilomenaWeb.Image.FileController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, update: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :verify_not_deleted
def update(conn, %{"image" => image_params}) do
case Images.update_file(conn.assigns.image, image_params) do
{:ok, %{image: image}} ->
spawn fn ->
Images.repair_image(image)
end
conn
|> put_flash(:info, "Successfully updated file.")
|> redirect(to: Routes.image_path(conn, :show, image))
_error ->
conn
|> put_flash(:error, "Failed to update file!")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
end
end
defp verify_not_deleted(conn, _opts) do
case conn.assigns.image.hidden_from_users do
true ->
conn
|> put_flash(:error, "Cannot replace a hidden image.")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
_false ->
conn
end
end
end

View file

@ -0,0 +1,17 @@
defmodule PhilomenaWeb.Image.HashController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, delete: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def delete(conn, _params) do
{:ok, image} = Images.remove_hash(conn.assigns.image)
conn
|> put_flash(:info, "Successfully cleared hash.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -0,0 +1,32 @@
defmodule PhilomenaWeb.Image.RepairController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
plug :verify_not_deleted
def create(conn, _params) do
spawn fn ->
Images.repair_image(conn.assigns.image)
end
conn
|> put_flash(:info, "Repair job started.")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
end
defp verify_not_deleted(conn, _opts) do
case conn.assigns.image.hidden_from_users do
true ->
conn
|> put_flash(:error, "Cannot repair a hidden image.")
|> redirect(to: Routes.image_path(conn, :show, conn.assigns.image))
_false ->
conn
end
end
end

View file

@ -0,0 +1,22 @@
defmodule PhilomenaWeb.Image.ScratchpadController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, edit: :hide, update: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def edit(conn, _params) do
changeset = Images.change_image(conn.assigns.image)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"image" => image_params}) do
{:ok, image} = Images.update_scratchpad(conn.assigns.image, image_params)
conn
|> put_flash(:info, "Successfully updated moderation notes.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -0,0 +1,19 @@
defmodule PhilomenaWeb.Image.SourceHistoryController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, delete: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def delete(conn, _params) do
{:ok, image} = Images.remove_source_history(conn.assigns.image)
Images.reindex_image(image)
conn
|> put_flash(:info, "Successfully deleted source history.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -0,0 +1,25 @@
defmodule PhilomenaWeb.Image.TagLockController do
use PhilomenaWeb, :controller
alias Philomena.Images.Image
alias Philomena.Images
plug PhilomenaWeb.CanaryMapPlug, create: :hide, delete: :hide
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
def create(conn, _params) do
{:ok, image} = Images.lock_tags(conn.assigns.image, true)
conn
|> put_flash(:info, "Successfully locked tags.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
def delete(conn, _params) do
{:ok, image} = Images.lock_tags(conn.assigns.image, false)
conn
|> put_flash(:info, "Successfully unlocked tags.")
|> redirect(to: Routes.image_path(conn, :show, image))
end
end

View file

@ -120,6 +120,17 @@ defmodule PhilomenaWeb.Router do
resources "/delete", Image.Comment.DeleteController, only: [:create], singleton: true
end
resources "/delete", Image.DeleteController, only: [:create, :delete], singleton: true
resources "/hash", Image.HashController, only: [:delete], singleton: true
resources "/source_history", Image.SourceHistoryController, only: [:delete], singleton: true
resources "/repair", Image.RepairController, only: [:create], singleton: true
resources "/feature", Image.FeatureController, only: [:create], singleton: true
resources "/file", Image.FileController, only: [:update], singleton: true
resources "/scratchpad", Image.ScratchpadController, only: [:edit, :update], singleton: true
resources "/comment_lock", Image.CommentLockController, only: [:create, :delete], singleton: true
resources "/description_lock", Image.DescriptionLockController, only: [:create, :delete], singleton: true
resources "/tag_lock", Image.TagLockController, only: [:create, :delete], singleton: true
end
resources "/forums", ForumController, only: [] do

View file

@ -65,15 +65,18 @@
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}"
= 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...' }
= form_for @changeset, Routes.image_file_path(@conn, :update, @image), [method: "put", multipart: true], fn f ->
#js-image-upload-previews
p Upload a file from your computer
.field = file_input f, :image, class: "input js-scraper"
= submit "Save changes", class: "button", data: [disable_with: raw("Replacing…")]
.block__tab.hidden data-tab="administration"
.block.block--danger
a.button.button--link> href="#"
a.button.button--link> href=Routes.image_scratchpad_path(@conn, :edit, @image)
i.far.fa-edit
= if present?(@image.scratchpad) do
strong> Mod notes:
@ -90,3 +93,37 @@
- else
= button_to "Restore", Routes.image_delete_path(@conn, :delete, @image), method: "delete", class: "button button--state-success"
.flex.flex--spaced-out.flex--wrap
= if not @image.hidden_from_users do
= form_for @changeset, Routes.image_feature_path(@conn, :create, @image), [method: "post"], fn _f ->
.field
p Automatically tags 'featured image'
= submit "Feature", data: [confirm: "Are you really, really sure?"], class: "button button--state-success"
= form_for @changeset, Routes.image_repair_path(@conn, :create, @image), [method: "post"], fn _f ->
.field
= submit "Repair", class: "button button--state-success"
= form_for @changeset, Routes.image_hash_path(@conn, :delete, @image), [method: "delete"], fn _f ->
.field
p Allows reuploading the image
.flex.flex--end-bunched
= submit "Clear hash", data: [confirm: "Are you really, really sure?"], class: "button button--state-danger"
br
.flex.flex--spaced-out
= if @image.commenting_allowed do
= button_to "Lock commenting", Routes.image_comment_lock_path(@conn, :create, @image), method: "post", class: "button"
- else
= button_to "Unlock commenting", Routes.image_comment_lock_path(@conn, :delete, @image), method: "delete", class: "button"
= if @image.description_editing_allowed do
= button_to "Lock description editing", Routes.image_description_lock_path(@conn, :create, @image), method: "post", class: "button"
- else
= button_to "Unlock description editing", Routes.image_description_lock_path(@conn, :delete, @image), method: "delete", class: "button"
= if @image.tag_editing_allowed do
= 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"

View file

@ -41,3 +41,9 @@
| History (
= @source_change_count
| )
= if can?(@conn, :hide, @image) do
= form_for @changeset, Routes.image_source_history_path(@conn, :delete, @image), [method: "delete"], fn _f ->
button.button.button--state-danger.button--separate-left type="submit" data-confirm="Are you really, really sure?" title="Wipe sources"
i.fas.fa-eraser>
' Wipe

View file

@ -0,0 +1,9 @@
h1
' Editing moderation notes for image
= link "##{@image.id}", to: Routes.image_path(@conn, :show, @image)
= form_for @changeset, Routes.image_scratchpad_path(@conn, :update, @image), fn f ->
.field
= textarea f, :scratchpad, placeholder: "Scratchpad contents", class: "input input--wide"
= submit "Update", class: "button", data: [disable_with: raw("Saving…")]

View file

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