mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
approval queue stuff
This commit is contained in:
parent
eeb2f851e0
commit
26deaaf588
44 changed files with 449 additions and 82 deletions
|
@ -470,6 +470,7 @@ span.stat {
|
|||
@import "text";
|
||||
|
||||
@import "~views/adverts";
|
||||
@import "~views/approval";
|
||||
@import "~views/badges";
|
||||
@import "~views/channels";
|
||||
@import "~views/comments";
|
||||
|
|
49
assets/css/views/_approval.scss
Normal file
49
assets/css/views/_approval.scss
Normal file
|
@ -0,0 +1,49 @@
|
|||
.approval-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
grid-template-rows: 1fr;
|
||||
gap: var(--padding-small);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.approval-items--main {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr 1fr 1fr;
|
||||
gap: var(--padding-small);
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.approval-items--main span {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.approval-items--footer {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 1fr;
|
||||
gap: var(--padding-normal);
|
||||
}
|
||||
|
||||
.approval-items--footer * {
|
||||
height: 2rem;
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
@media (max-width: $min_px_width_for_desktop_layout) {
|
||||
.approval-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:
|
||||
"main"
|
||||
"footer";
|
||||
}
|
||||
|
||||
.approval-items--main {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
.approval-items--footer {
|
||||
grid-area: footer;
|
||||
}
|
||||
}
|
26
assets/js/pmwarning.js
Normal file
26
assets/js/pmwarning.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* PmWarning
|
||||
*
|
||||
* Warn users that their PM will be reviewed.
|
||||
*/
|
||||
|
||||
import { $ } from './utils/dom';
|
||||
|
||||
function warnAboutPMs() {
|
||||
const textarea = $('.js-toolbar-input');
|
||||
const warning = $('.js-hidden-warning');
|
||||
const imageEmbedRegex = /!+\[/g;
|
||||
|
||||
if (!warning || !textarea) return;
|
||||
|
||||
textarea.addEventListener('input', () => {
|
||||
const value = textarea.value;
|
||||
|
||||
if (value.match(imageEmbedRegex))
|
||||
warning.classList.remove('hidden');
|
||||
else if (!warning.classList.contains('hidden'))
|
||||
warning.classList.add('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
export { warnAboutPMs };
|
|
@ -34,6 +34,7 @@ import { setupSearch } from './search';
|
|||
import { setupToolbar } from './markdowntoolbar';
|
||||
import { hideStaffTools } from './staffhider';
|
||||
import { pollOptionCreator } from './poll';
|
||||
import { warnAboutPMs } from './pmwarning';
|
||||
|
||||
whenReady(() => {
|
||||
|
||||
|
@ -66,5 +67,6 @@ whenReady(() => {
|
|||
setupToolbar();
|
||||
hideStaffTools();
|
||||
pollOptionCreator();
|
||||
warnAboutPMs();
|
||||
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Philomena.Comments do
|
|||
|
||||
alias Philomena.Elasticsearch
|
||||
alias Philomena.Reports.Report
|
||||
alias Philomena.UserStatistics
|
||||
alias Philomena.Comments.Comment
|
||||
alias Philomena.Comments.ElasticsearchIndex, as: CommentIndex
|
||||
alias Philomena.IndexWorker
|
||||
|
@ -212,6 +213,8 @@ defmodule Philomena.Comments do
|
|||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{comment: comment, reports: {_count, reports}}} ->
|
||||
notify_comment(comment)
|
||||
UserStatistics.inc_stat(comment.user, :comments_posted)
|
||||
Reports.reindex_reports(reports)
|
||||
reindex_comment(comment)
|
||||
|
||||
|
@ -222,6 +225,8 @@ defmodule Philomena.Comments do
|
|||
end
|
||||
end
|
||||
|
||||
def report_non_approved(%Comment{approved: true}), do: false
|
||||
|
||||
def report_non_approved(comment) do
|
||||
Reports.create_system_report(
|
||||
comment.id,
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule Philomena.Conversations do
|
|||
import Ecto.Query, warn: false
|
||||
alias Ecto.Multi
|
||||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Reports
|
||||
alias Philomena.Conversations.Conversation
|
||||
|
||||
@doc """
|
||||
|
@ -187,6 +187,12 @@ defmodule Philomena.Conversations do
|
|||
Ecto.build_assoc(conversation, :messages)
|
||||
|> Message.creation_changeset(attrs, user)
|
||||
|
||||
show_as_read =
|
||||
case message do
|
||||
%{changes: %{approved: true}} -> false
|
||||
_ -> true
|
||||
end
|
||||
|
||||
conversation_query =
|
||||
Conversation
|
||||
|> where(id: ^conversation.id)
|
||||
|
@ -196,11 +202,43 @@ defmodule Philomena.Conversations do
|
|||
Multi.new()
|
||||
|> Multi.insert(:message, message)
|
||||
|> Multi.update_all(:conversation, conversation_query,
|
||||
set: [from_read: false, to_read: false, last_message_at: now]
|
||||
set: [from_read: show_as_read, to_read: show_as_read, last_message_at: now]
|
||||
)
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def approve_conversation_message(message) do
|
||||
message_query =
|
||||
message
|
||||
|> Message.approve_changeset()
|
||||
|
||||
conversation_query =
|
||||
Conversation
|
||||
|> where(id: ^message.conversation_id)
|
||||
|
||||
now = DateTime.utc_now()
|
||||
|
||||
Multi.new()
|
||||
|> Multi.update(:message, message_query)
|
||||
|> Multi.update_all(:conversation, conversation_query, set: [to_read: false])
|
||||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
def report_non_approved(id) do
|
||||
Reports.create_system_report(
|
||||
id,
|
||||
"Conversation",
|
||||
"Approval",
|
||||
"PM contains externally-embedded images and has been flagged for review."
|
||||
)
|
||||
end
|
||||
|
||||
def set_as_read(conversation) do
|
||||
conversation
|
||||
|> Conversation.to_read_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a message.
|
||||
|
||||
|
|
|
@ -37,6 +37,11 @@ defmodule Philomena.Conversations.Conversation do
|
|||
|> cast(attrs, [:from_read, :to_read])
|
||||
end
|
||||
|
||||
def to_read_changeset(conversation) do
|
||||
change(conversation)
|
||||
|> put_change(:to_read, true)
|
||||
end
|
||||
|
||||
def hidden_changeset(conversation, attrs) do
|
||||
conversation
|
||||
|> cast(attrs, [:from_hidden, :to_hidden])
|
||||
|
|
|
@ -11,6 +11,7 @@ defmodule Philomena.Conversations.Message do
|
|||
belongs_to :from, User
|
||||
|
||||
field :body, :string
|
||||
field :approved, :boolean, default: false
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
@ -31,4 +32,8 @@ defmodule Philomena.Conversations.Message do
|
|||
|> validate_length(:body, max: 300_000, count: :bytes)
|
||||
|> Approval.maybe_put_approval(user)
|
||||
end
|
||||
|
||||
def approve_changeset(message) do
|
||||
change(message, approved: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -115,7 +115,7 @@ defmodule Philomena.Images do
|
|||
repair_image(image)
|
||||
reindex_image(image)
|
||||
Tags.reindex_tags(image.added_tags)
|
||||
UserStatistics.inc_stat(attribution[:user], :uploads)
|
||||
maybe_approve_image(image, attribution[:user])
|
||||
|
||||
result
|
||||
|
||||
|
@ -135,6 +135,55 @@ defmodule Philomena.Images do
|
|||
multi
|
||||
end
|
||||
|
||||
def approve_image(image) do
|
||||
image
|
||||
|> Repo.preload(:user)
|
||||
|> Image.approve_changeset()
|
||||
|> Repo.update()
|
||||
|> case do
|
||||
{:ok, image} ->
|
||||
reindex_image(image)
|
||||
increment_user_stats(image.user)
|
||||
maybe_suggest_user_verification(image.user)
|
||||
|
||||
{:ok, image}
|
||||
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_approve_image(image, nil), do: false
|
||||
|
||||
defp maybe_approve_image(image, %User{verified: false, role: role}) when role == "user",
|
||||
do: false
|
||||
|
||||
defp maybe_approve_image(image, _user), do: approve_image(image)
|
||||
|
||||
defp increment_user_stats(nil), do: false
|
||||
|
||||
defp increment_user_stats(%User{} = user) do
|
||||
UserStatistics.inc_stat(user, :uploads)
|
||||
end
|
||||
|
||||
defp maybe_suggest_user_verification(%User{id: id, uploads_count: 5, verified: false}) do
|
||||
Reports.create_system_report(
|
||||
id,
|
||||
"User",
|
||||
"Verification",
|
||||
"User has uploaded enough approved images to be considered for verification."
|
||||
)
|
||||
end
|
||||
|
||||
defp maybe_suggest_user_verification(_user), do: false
|
||||
|
||||
def count_pending_approvals() do
|
||||
Image
|
||||
|> where(hidden_from_users: false)
|
||||
|> where(approved: false)
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
def feature_image(featurer, %Image{} = image) do
|
||||
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
||||
|> ImageFeature.changeset(%{})
|
||||
|
|
|
@ -326,6 +326,12 @@ defmodule Philomena.Images.Image do
|
|||
cast(image, attrs, [:anonymous])
|
||||
end
|
||||
|
||||
def approve_changeset(image) do
|
||||
change(image)
|
||||
|> put_change(:approved, true)
|
||||
|> put_change(:first_seen_at, DateTime.truncate(DateTime.utc_now(), :second))
|
||||
end
|
||||
|
||||
def cache_changeset(image) do
|
||||
changeset = change(image)
|
||||
image = apply_changes(changeset)
|
||||
|
|
|
@ -10,6 +10,7 @@ defmodule Philomena.Posts do
|
|||
alias Philomena.Elasticsearch
|
||||
alias Philomena.Topics.Topic
|
||||
alias Philomena.Topics
|
||||
alias Philomena.UserStatistics
|
||||
alias Philomena.Posts.Post
|
||||
alias Philomena.Posts.ElasticsearchIndex, as: PostIndex
|
||||
alias Philomena.IndexWorker
|
||||
|
@ -117,6 +118,8 @@ defmodule Philomena.Posts do
|
|||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
|
||||
end
|
||||
|
||||
def report_non_approved(%Post{approved: true}), do: false
|
||||
|
||||
def report_non_approved(post) do
|
||||
Reports.create_system_report(
|
||||
post.id,
|
||||
|
@ -261,6 +264,8 @@ defmodule Philomena.Posts do
|
|||
|> Repo.transaction()
|
||||
|> case do
|
||||
{:ok, %{post: post, reports: {_count, reports}}} ->
|
||||
notify_post(post)
|
||||
UserStatistics.inc_stat(post.user, :forum_posts)
|
||||
Reports.reindex_reports(reports)
|
||||
reindex_post(post)
|
||||
|
||||
|
|
|
@ -31,7 +31,8 @@ defmodule Philomena.Reports.ElasticsearchIndex do
|
|||
reportable_type: %{type: "keyword"},
|
||||
reportable_id: %{type: "keyword"},
|
||||
open: %{type: "boolean"},
|
||||
reason: %{type: "text", analyzer: "snowball"}
|
||||
reason: %{type: "text", analyzer: "snowball"},
|
||||
system: %{type: "boolean"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +54,8 @@ defmodule Philomena.Reports.ElasticsearchIndex do
|
|||
reportable_id: report.reportable_id,
|
||||
fingerprint: report.fingerprint,
|
||||
open: report.open,
|
||||
reason: report.reason
|
||||
reason: report.reason,
|
||||
system: report.system
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ defmodule Philomena.Topics do
|
|||
|> case do
|
||||
{:ok, %{topic: topic}} = result ->
|
||||
Posts.reindex_post(hd(topic.posts))
|
||||
Posts.report_non_approved(hd(topic.posts))
|
||||
|
||||
result
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ defmodule Philomena.Topics.Topic do
|
|||
field :slug, :string
|
||||
field :anonymous, :boolean, default: false
|
||||
field :hidden_from_users, :boolean, default: false
|
||||
field :approved, :boolean
|
||||
|
||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||
end
|
||||
|
|
|
@ -273,9 +273,6 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
|||
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Topic{}),
|
||||
do: true
|
||||
|
||||
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :approve, %Topic{}),
|
||||
do: true
|
||||
|
||||
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Post{}),
|
||||
do: true
|
||||
|
||||
|
|
28
lib/philomena_web/controllers/admin/approval_controller.ex
Normal file
28
lib/philomena_web/controllers/admin/approval_controller.ex
Normal file
|
@ -0,0 +1,28 @@
|
|||
defmodule PhilomenaWeb.Admin.ApprovalController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Images.Image
|
||||
alias Philomena.Repo
|
||||
import Ecto.Query
|
||||
|
||||
plug :verify_authorized
|
||||
|
||||
def index(conn, _params) do
|
||||
images =
|
||||
Image
|
||||
|> where(hidden_from_users: false)
|
||||
|> where(approved: false)
|
||||
|> order_by(desc: :id)
|
||||
|> preload([:user, tags: [:aliases, :aliased_tag]])
|
||||
|> Repo.paginate(conn.assigns.scrivener)
|
||||
|
||||
render(conn, "index.html", title: "Admin - Approval Queue", images: images)
|
||||
end
|
||||
|
||||
defp verify_authorized(conn, _opts) do
|
||||
case Canada.Can.can?(conn.assigns.current_user, :approve, %Image{}) do
|
||||
true -> conn
|
||||
false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -42,7 +42,10 @@ defmodule PhilomenaWeb.Admin.ReportController do
|
|||
%{
|
||||
bool: %{
|
||||
must: %{term: %{open: true}},
|
||||
must_not: %{term: %{admin_id: user.id}}
|
||||
must_not: [
|
||||
%{term: %{admin_id: user.id}},
|
||||
%{term: %{system: true}}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -59,11 +62,20 @@ defmodule PhilomenaWeb.Admin.ReportController do
|
|||
|> Repo.all()
|
||||
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
|
||||
|
||||
system_reports =
|
||||
Report
|
||||
|> where(open: true, system: true)
|
||||
|> preload([:admin, user: :linked_tags])
|
||||
|> order_by(desc: :created_at)
|
||||
|> Repo.all()
|
||||
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
|
||||
|
||||
render(conn, "index.html",
|
||||
title: "Admin - Reports",
|
||||
layout_class: "layout--wide",
|
||||
reports: reports,
|
||||
my_reports: my_reports
|
||||
my_reports: my_reports,
|
||||
system_reports: system_reports
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
defmodule PhilomenaWeb.Conversation.ApproveController do
|
||||
defmodule PhilomenaWeb.Conversation.Message.ApproveController do
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Conversations.Conversation
|
||||
alias Philomena.Conversations.Message
|
||||
alias Philomena.Conversations
|
||||
|
||||
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||
|
||||
plug :load_and_authorize_resource,
|
||||
model: Conversation,
|
||||
id_field: "slug",
|
||||
id_name: "conversation_id",
|
||||
persisted: true
|
||||
model: Message,
|
||||
id_name: "message_id",
|
||||
persisted: true,
|
||||
preload: [:conversation]
|
||||
|
||||
def create(conn, _params) do
|
||||
message = conn.assigns.message
|
|
@ -20,7 +20,11 @@ defmodule PhilomenaWeb.Conversation.MessageController do
|
|||
user = conn.assigns.current_user
|
||||
|
||||
case Conversations.create_message(conversation, user, message_params) do
|
||||
{:ok, _result} ->
|
||||
{:ok, %{message: message}} ->
|
||||
if not message.approved do
|
||||
Conversations.report_non_approved(message.conversation_id)
|
||||
end
|
||||
|
||||
count =
|
||||
Message
|
||||
|> where(conversation_id: ^conversation.id)
|
||||
|
|
|
@ -107,6 +107,11 @@ defmodule PhilomenaWeb.ConversationController do
|
|||
|
||||
case Conversations.create_conversation(user, conversation_params) do
|
||||
{:ok, conversation} ->
|
||||
if not hd(conversation.messages).approved do
|
||||
Conversations.report_non_approved(conversation.id)
|
||||
Conversations.set_as_read(conversation)
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Conversation successfully created.")
|
||||
|> redirect(to: Routes.conversation_path(conn, :show, conversation))
|
||||
|
|
|
@ -15,7 +15,7 @@ defmodule PhilomenaWeb.Image.ApproveController do
|
|||
conn
|
||||
|> put_flash(:info, "Image has been approved.")
|
||||
|> moderation_log(details: &log_details/3, data: image)
|
||||
|> redirect(to: Routes.image_path(conn, :show, image))
|
||||
|> redirect(to: Routes.admin_approval_path(conn, :index))
|
||||
end
|
||||
|
||||
defp log_details(conn, _action, image) do
|
||||
|
|
|
@ -78,11 +78,11 @@ defmodule PhilomenaWeb.Image.CommentController do
|
|||
PhilomenaWeb.Api.Json.CommentView.render("show.json", %{comment: comment})
|
||||
)
|
||||
|
||||
Comments.notify_comment(comment)
|
||||
Comments.reindex_comment(comment)
|
||||
Images.reindex_image(conn.assigns.image)
|
||||
|
||||
if comment.approved do
|
||||
Comments.notify_comment(comment)
|
||||
UserStatistics.inc_stat(conn.assigns.current_user, :comments_posted)
|
||||
else
|
||||
Comments.report_non_approved(comment)
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
defmodule PhilomenaWeb.Topic.ApproveController do
|
||||
import Plug.Conn
|
||||
use PhilomenaWeb, :controller
|
||||
|
||||
alias Philomena.Forums.Forum
|
||||
alias Philomena.Topics.Topic
|
||||
alias Philomena.Topics
|
||||
|
||||
plug PhilomenaWeb.CanaryMapPlug, create: :show
|
||||
|
||||
plug :load_and_authorize_resource,
|
||||
model: Forum,
|
||||
id_name: "forum_id",
|
||||
id_field: "short_name",
|
||||
persisted: true
|
||||
|
||||
plug PhilomenaWeb.LoadTopicPlug
|
||||
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||
plug :authorize_resource, model: Topic, persisted: true
|
||||
|
||||
def create(conn, _params) do
|
||||
topic = conn.assigns.topic
|
||||
user = conn.assigns.current_user
|
||||
|
||||
case Topics.approve_topic(topic) do
|
||||
{:ok, topic} ->
|
||||
conn
|
||||
|> put_flash(:info, "Topic successfully approved!")
|
||||
|> moderation_log(details: &log_details/3, data: topic)
|
||||
|> redirect(to: Routes.forum_topic_path(conn, :show, topic.forum, topic))
|
||||
|
||||
{:error, _changeset} ->
|
||||
conn
|
||||
|> put_flash(:error, "Unable to approve the topic!")
|
||||
|> redirect(to: Routes.forum_topic_path(conn, :show, topic.forum, topic))
|
||||
end
|
||||
end
|
||||
|
||||
defp log_details(conn, action, topic) do
|
||||
%{
|
||||
body: "Approved topic '#{topic.title}' in #{topic.forum.name}",
|
||||
subject_path: Routes.forum_topic_path(conn, :show, topic.forum, topic)
|
||||
}
|
||||
end
|
||||
end
|
|
@ -18,8 +18,6 @@ defmodule PhilomenaWeb.Topic.Post.ApproveController do
|
|||
|
||||
case Posts.approve_post(post, user) do
|
||||
{:ok, post} ->
|
||||
UserStatistics.inc_stat(post.user(:forum_posts))
|
||||
|
||||
conn
|
||||
|> put_flash(:info, "Post successfully approved.")
|
||||
|> moderation_log(details: &log_details/3, data: post)
|
||||
|
|
|
@ -35,9 +35,8 @@ defmodule PhilomenaWeb.Topic.PostController do
|
|||
|
||||
case Posts.create_post(topic, attributes, post_params) do
|
||||
{:ok, %{post: post}} ->
|
||||
Posts.notify_post(post)
|
||||
|
||||
if post.approved do
|
||||
Posts.notify_post(post)
|
||||
UserStatistics.inc_stat(conn.assigns.current_user, :forum_posts)
|
||||
else
|
||||
Posts.report_non_approved(post)
|
||||
|
|
|
@ -63,6 +63,7 @@ defmodule PhilomenaWeb.ImageLoader do
|
|||
]
|
||||
|> maybe_show_deleted(show_hidden?, del)
|
||||
|> maybe_custom_hide(user, hidden)
|
||||
|> hide_non_approved()
|
||||
end
|
||||
|
||||
# Allow moderators to index hidden images
|
||||
|
@ -94,6 +95,10 @@ defmodule PhilomenaWeb.ImageLoader do
|
|||
defp maybe_custom_hide(filters, _user, _param),
|
||||
do: filters
|
||||
|
||||
# Hide all images that aren't approved from all search queries.
|
||||
defp hide_non_approved(filters),
|
||||
do: [%{term: %{approved: false}} | filters]
|
||||
|
||||
# TODO: the search parser should try to optimize queries
|
||||
defp search_tag_name(%{term: %{"namespaced_tags.name" => tag_name}}), do: [tag_name]
|
||||
defp search_tag_name(_other_query), do: []
|
||||
|
|
|
@ -75,7 +75,7 @@ defmodule PhilomenaWeb.ImageSorter do
|
|||
end
|
||||
|
||||
defp parse_sf(_params, sd, query) do
|
||||
%{query: query, sorts: [%{"id" => sd}]}
|
||||
%{query: query, sorts: [%{"first_seen_at" => sd}]}
|
||||
end
|
||||
|
||||
defp random_query(seed, sd, query) do
|
||||
|
|
|
@ -58,6 +58,9 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
|||
image.hidden_from_users ->
|
||||
" (deleted)"
|
||||
|
||||
not image.approved ->
|
||||
" (pending approval)"
|
||||
|
||||
true ->
|
||||
""
|
||||
end
|
||||
|
@ -75,7 +78,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
|||
cond do
|
||||
img != nil ->
|
||||
case group do
|
||||
[_id, "p"] when not img.hidden_from_users ->
|
||||
[_id, "p"] when not img.hidden_from_users and img.approved ->
|
||||
Phoenix.View.render(@image_view, "_image_target.html",
|
||||
image: img,
|
||||
size: :medium,
|
||||
|
@ -83,7 +86,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
|||
)
|
||||
|> safe_to_string()
|
||||
|
||||
[_id, "t"] when not img.hidden_from_users ->
|
||||
[_id, "t"] when not img.hidden_from_users and img.approved ->
|
||||
Phoenix.View.render(@image_view, "_image_target.html",
|
||||
image: img,
|
||||
size: :small,
|
||||
|
@ -91,7 +94,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
|||
)
|
||||
|> safe_to_string()
|
||||
|
||||
[_id, "s"] when not img.hidden_from_users ->
|
||||
[_id, "s"] when not img.hidden_from_users and img.approved ->
|
||||
Phoenix.View.render(@image_view, "_image_target.html",
|
||||
image: img,
|
||||
size: :thumb_small,
|
||||
|
@ -99,6 +102,9 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
|||
)
|
||||
|> safe_to_string()
|
||||
|
||||
[id, suffix] when not img.approved ->
|
||||
">>#{img.id}#{suffix}#{link_suffix(img)}"
|
||||
|
||||
[_id, ""] ->
|
||||
link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}")
|
||||
|> safe_to_string()
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule PhilomenaWeb.AdminCountersPlug do
|
|||
alias Philomena.Reports
|
||||
alias Philomena.ArtistLinks
|
||||
alias Philomena.DnpEntries
|
||||
alias Philomena.Images
|
||||
|
||||
import Plug.Conn, only: [assign: 3]
|
||||
|
||||
|
@ -31,12 +32,14 @@ defmodule PhilomenaWeb.AdminCountersPlug do
|
|||
defp maybe_assign_admin_metrics(conn, _user, false), do: conn
|
||||
|
||||
defp maybe_assign_admin_metrics(conn, user, true) do
|
||||
pending_approvals = Images.count_pending_approvals()
|
||||
duplicate_reports = DuplicateReports.count_duplicate_reports(user)
|
||||
reports = Reports.count_reports(user)
|
||||
artist_links = ArtistLinks.count_artist_links(user)
|
||||
dnps = DnpEntries.count_dnp_entries(user)
|
||||
|
||||
conn
|
||||
|> assign(:pending_approval_count, pending_approvals)
|
||||
|> assign(:duplicate_report_count, duplicate_reports)
|
||||
|> assign(:report_count, reports)
|
||||
|> assign(:artist_link_count, artist_links)
|
||||
|
|
|
@ -177,10 +177,15 @@ defmodule PhilomenaWeb.Router do
|
|||
|
||||
resources "/conversations", ConversationController, only: [:index, :show, :new, :create] do
|
||||
resources "/reports", Conversation.ReportController, only: [:new, :create]
|
||||
resources "/messages", Conversation.MessageController, only: [:create]
|
||||
|
||||
resources "/messages", Conversation.MessageController, only: [:create] do
|
||||
resources "/approve", Conversation.Message.ApproveController,
|
||||
only: [:create],
|
||||
singleton: true
|
||||
end
|
||||
|
||||
resources "/read", Conversation.ReadController, only: [:create, :delete], singleton: true
|
||||
resources "/hide", Conversation.HideController, only: [:create, :delete], singleton: true
|
||||
resources "/approve", Conversation.ApproveController, only: [:create], singleton: true
|
||||
end
|
||||
|
||||
resources "/images", ImageController, only: [] do
|
||||
|
@ -240,7 +245,6 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/stick", Topic.StickController, only: [:create, :delete], singleton: true
|
||||
resources "/lock", Topic.LockController, only: [:create, :delete], singleton: true
|
||||
resources "/hide", Topic.HideController, only: [:create, :delete], singleton: true
|
||||
resources "/approve", Topic.ApproveController, only: [:create], singleton: true
|
||||
|
||||
resources "/posts", Topic.PostController, only: [:edit, :update] do
|
||||
resources "/hide", Topic.Post.HideController, only: [:create, :delete], singleton: true
|
||||
|
@ -340,6 +344,8 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/close", Report.CloseController, only: [:create], singleton: true
|
||||
end
|
||||
|
||||
resources "/approvals", ApprovalController, only: [:index]
|
||||
|
||||
resources "/artist_links", ArtistLinkController, only: [:index] do
|
||||
resources "/verification", ArtistLink.VerificationController,
|
||||
only: [:create],
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
.block
|
||||
.block__header
|
||||
.block__header__title.approval-grid
|
||||
.approval-items--main
|
||||
span ID
|
||||
span Image
|
||||
span Uploader
|
||||
span Time
|
||||
.approval-items--footer.hide-mobile
|
||||
span.hide-mobile Actions
|
||||
= for image <- @images do
|
||||
.block__content.alternating-color
|
||||
.approval-grid
|
||||
.approval-items--main
|
||||
span = link ">>#{image.id}", to: Routes.image_path(@conn, :show, image)
|
||||
span = image_thumb(@conn, image)
|
||||
span
|
||||
= if image.user do
|
||||
= link image.user.name, to: Routes.profile_path(@conn, :show, image.user)
|
||||
- else
|
||||
em>
|
||||
= truncated_ip_link(@conn, image.ip)
|
||||
= link_to_fingerprint(@conn, image.fingerprint)
|
||||
span = pretty_time(image.created_at)
|
||||
.approval-items--footer
|
||||
= if can?(@conn, :approve, image) do
|
||||
= button_to "Approve", Routes.image_approve_path(@conn, :create, image), method: "post", class: "button button--state-success"
|
||||
= if can?(@conn, :hide, image) do
|
||||
= form_for :image, Routes.image_delete_path(@conn, :create, image), [method: "post"], fn f ->
|
||||
.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"
|
16
lib/philomena_web/templates/admin/approval/index.html.slime
Normal file
16
lib/philomena_web/templates/admin/approval/index.html.slime
Normal file
|
@ -0,0 +1,16 @@
|
|||
- route = fn p -> Routes.admin_approval_path(@conn, :index, p) end
|
||||
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @images, route: route
|
||||
|
||||
h1 Approval Queue
|
||||
|
||||
.block
|
||||
.block__header
|
||||
= pagination
|
||||
|
||||
= if Enum.any?(@images) do
|
||||
= render PhilomenaWeb.Admin.ApprovalView, "_approvals.html", images: @images, conn: @conn
|
||||
- else
|
||||
' No images are pending approval. Good job!
|
||||
|
||||
.block__header.block__header--light
|
||||
= pagination
|
|
@ -10,6 +10,13 @@ h1 Reports
|
|||
.block__content
|
||||
= render PhilomenaWeb.Admin.ReportView, "_reports.html", reports: @my_reports, conn: @conn
|
||||
|
||||
= if Enum.any?(@system_reports) do
|
||||
.block
|
||||
.block__header.block--danger
|
||||
span.block__header__title System Reports
|
||||
.block__content
|
||||
= render PhilomenaWeb.Admin.ReportView, "_reports.html", reports: @system_reports, conn: @conn
|
||||
|
||||
.block
|
||||
.block__header
|
||||
span.block__header__title All Reports
|
||||
|
|
|
@ -5,6 +5,21 @@ h1 New Conversation
|
|||
' »
|
||||
span.block__header__title New Conversation
|
||||
|
||||
= case DateTime.compare(DateTime.utc_now(), DateTime.add(@conn.assigns.current_user.created_at, 1_209_600)) do
|
||||
- :lt ->
|
||||
.block.block--fixed.block--warning.hidden.js-hidden-warning
|
||||
h2 Warning!
|
||||
p
|
||||
strong> Your account is too new, so your PM will need to be reviewed by staff members.
|
||||
' This is because it contains an external image. If you are not okay with a moderator viewing this PM conversation, please consider linking the image instead of embedding it (change
|
||||
code<> ![
|
||||
' to
|
||||
code<
|
||||
| [
|
||||
| ).
|
||||
- _ ->
|
||||
/ Nothing
|
||||
|
||||
= form_for @changeset, Routes.conversation_path(@conn, :create), fn f ->
|
||||
= if @changeset.action do
|
||||
.alert.alert-danger
|
||||
|
|
|
@ -31,6 +31,21 @@ h1 = @conversation.title
|
|||
.block__header.block__header--light.page__header
|
||||
.page__pagination = pagination
|
||||
|
||||
= case DateTime.compare(DateTime.utc_now(), DateTime.add(@conn.assigns.current_user.created_at, 1_209_600)) do
|
||||
- :lt ->
|
||||
.block.block--fixed.block--warning.hidden.js-hidden-warning
|
||||
h2 Warning!
|
||||
p
|
||||
strong> Your account is too new, so your PM will need to be reviewed by staff members.
|
||||
' This is because it contains an external image. If you are not okay with a moderator viewing this PM conversation, please consider linking the image instead of embedding it (change
|
||||
code<> ![
|
||||
' to
|
||||
code<
|
||||
| [
|
||||
| ).
|
||||
- _ ->
|
||||
/ Nothing
|
||||
|
||||
= cond do
|
||||
- @conn.assigns.current_ban ->
|
||||
= render PhilomenaWeb.BanView, "_ban_reason.html", conn: @conn
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
= if not @image.approved and not @image.hidden_from_users do
|
||||
.block.block--fixed.block--warning
|
||||
h2 Hold up!
|
||||
p This image is pending approval from a staff member. It will appear on the site once it's reviewed and approved.
|
||||
p
|
||||
' Don't worry,
|
||||
strong
|
||||
' the image will not lose any viewership,
|
||||
' it will appear on the homepage and in search results as normal (as if it was uploaded at the time of approval).
|
||||
p
|
||||
a href="/pages/approval"
|
||||
strong Click here to learn more about image approval and verification.
|
|
@ -145,5 +145,7 @@
|
|||
br
|
||||
.flex.flex--spaced-out
|
||||
= link "Lock specific tags", to: Routes.image_tag_lock_path(@conn, :show, @image), class: "button"
|
||||
= if not @image.approved and can?(@conn, :approve, @image) do
|
||||
= button_to "Approve image", Routes.image_approve_path(@conn, :create, @image), method: "post", class: "button button--state-success", data: [confirm: "Are you sure?"]
|
||||
= if @image.hidden_from_users and can?(@conn, :destroy, @image) do
|
||||
= 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?"]
|
||||
= 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?"]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
= render PhilomenaWeb.ImageView, "_image_approval_banner.html", image: @image, conn: @conn
|
||||
= render PhilomenaWeb.ImageView, "_image_meta.html", image: @image, watching: @watching, user_galleries: @user_galleries, changeset: @image_changeset, conn: @conn
|
||||
= render PhilomenaWeb.ImageView, "_image_page.html", image: @image, conn: @conn
|
||||
|
||||
|
|
|
@ -46,6 +46,12 @@
|
|||
i.fa.fa-fw.fa-list-alt>
|
||||
' Mod Logs
|
||||
|
||||
= if @pending_approval_count do
|
||||
= link to: Routes.admin_approval_path(@conn, :index), class: "header__link", title: "Approval Queue" do
|
||||
' Q
|
||||
span.header__counter__admin
|
||||
= @pending_approval_count
|
||||
|
||||
= if @duplicate_report_count do
|
||||
= link to: Routes.duplicate_report_path(@conn, :index), class: "header__link", title: "Duplicates" do
|
||||
' D
|
||||
|
|
|
@ -1,4 +1,18 @@
|
|||
article.block.communication
|
||||
= if not @message.approved and (can?(@conn, :approve, @message) or @message.from_id == @conn.assigns.current_user.id) do
|
||||
.block__content
|
||||
.block.block--fixed.block--danger
|
||||
p
|
||||
i.fas.fa-exclamation-triangle>
|
||||
' This private message is pending approval from a staff member.
|
||||
= if can?(@conn, :approve, @message) do
|
||||
p
|
||||
ul.horizontal-list
|
||||
li
|
||||
= link(to: Routes.conversation_message_approve_path(@conn, :create, @message.conversation_id, @message), data: [confirm: "Are you sure?"], method: "post", class: "button") do
|
||||
i.fas.fa-check>
|
||||
' Approve
|
||||
|
||||
.block__content.flex.flex--no-wrap
|
||||
.flex__fixed.spacing-right
|
||||
= render PhilomenaWeb.UserAttributionView, "_user_avatar.html", object: %{user: @message.from}, conn: @conn, class: "avatar--100px"
|
||||
|
|
|
@ -133,9 +133,9 @@ h1 Search
|
|||
end
|
||||
|
||||
sort_fields = [
|
||||
"Sort by initial post date": :first_seen_at,
|
||||
"Sort by image ID": :id,
|
||||
"Sort by last modification date": :updated_at,
|
||||
"Sort by initial post date": :first_seen_at,
|
||||
"Sort by aspect ratio": :aspect_ratio,
|
||||
"Sort by fave count": :faves,
|
||||
"Sort by upvotes": :upvotes,
|
||||
|
|
16
lib/philomena_web/views/admin/approval_view.ex
Normal file
16
lib/philomena_web/views/admin/approval_view.ex
Normal file
|
@ -0,0 +1,16 @@
|
|||
defmodule PhilomenaWeb.Admin.ApprovalView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
alias PhilomenaWeb.Admin.ReportView
|
||||
|
||||
# Shamelessly copied from ReportView
|
||||
def truncated_ip_link(conn, ip), do: ReportView.truncated_ip_link(conn, ip)
|
||||
|
||||
def image_thumb(conn, image) do
|
||||
render(PhilomenaWeb.ImageView, "_image_container.html",
|
||||
image: image,
|
||||
size: :thumb_tiny,
|
||||
conn: conn
|
||||
)
|
||||
end
|
||||
end
|
|
@ -18,12 +18,18 @@ defmodule Philomena.Repo.Migrations.AddApprovalQueue do
|
|||
add :approved, :boolean, default: false
|
||||
end
|
||||
|
||||
alter table("topics") do
|
||||
alter table("messages") do
|
||||
add :approved, :boolean, default: false
|
||||
end
|
||||
|
||||
alter table("users") do
|
||||
add :verified, :boolean, default: false
|
||||
end
|
||||
|
||||
create index(:images, [:hidden_from_users, :approved],
|
||||
where: "hidden_from_users = false and approved = false"
|
||||
)
|
||||
|
||||
create index(:reports, [:system], where: "system = true")
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1007,7 +1007,8 @@ CREATE TABLE public.messages (
|
|||
updated_at timestamp without time zone NOT NULL,
|
||||
from_id integer NOT NULL,
|
||||
conversation_id integer NOT NULL,
|
||||
body character varying NOT NULL
|
||||
body character varying NOT NULL,
|
||||
approved boolean DEFAULT false
|
||||
);
|
||||
|
||||
|
||||
|
@ -1680,8 +1681,7 @@ CREATE TABLE public.topics (
|
|||
deleted_by_id integer,
|
||||
locked_by_id integer,
|
||||
last_post_id integer,
|
||||
hidden_from_users boolean DEFAULT false NOT NULL,
|
||||
approved boolean DEFAULT false
|
||||
hidden_from_users boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -2903,6 +2903,13 @@ CREATE UNIQUE INDEX image_tag_locks_image_id_tag_id_index ON public.image_tag_lo
|
|||
CREATE INDEX image_tag_locks_tag_id_index ON public.image_tag_locks USING btree (tag_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: images_hidden_from_users_approved_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX images_hidden_from_users_approved_index ON public.images USING btree (hidden_from_users, approved) WHERE ((hidden_from_users = false) AND (approved = false));
|
||||
|
||||
|
||||
--
|
||||
-- Name: index_adverts_on_restrictions; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -4100,6 +4107,13 @@ CREATE INDEX moderation_logs_user_id_created_at_index ON public.moderation_logs
|
|||
CREATE INDEX moderation_logs_user_id_index ON public.moderation_logs USING btree (user_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: reports_system_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE INDEX reports_system_index ON public.reports USING btree (system) WHERE (system = true);
|
||||
|
||||
|
||||
--
|
||||
-- Name: user_tokens_context_token_index; Type: INDEX; Schema: public; Owner: -
|
||||
--
|
||||
|
|
Loading…
Reference in a new issue