mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-24 04:27:59 +01:00
commit
b6248e22ee
66 changed files with 1031 additions and 44 deletions
|
@ -470,6 +470,7 @@ span.stat {
|
||||||
@import "text";
|
@import "text";
|
||||||
|
|
||||||
@import "~views/adverts";
|
@import "~views/adverts";
|
||||||
|
@import "~views/approval";
|
||||||
@import "~views/badges";
|
@import "~views/badges";
|
||||||
@import "~views/channels";
|
@import "~views/channels";
|
||||||
@import "~views/comments";
|
@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;
|
||||||
|
}
|
||||||
|
}
|
28
assets/js/pmwarning.js
Normal file
28
assets/js/pmwarning.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/**
|
||||||
|
* 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 { setupToolbar } from './markdowntoolbar';
|
||||||
import { hideStaffTools } from './staffhider';
|
import { hideStaffTools } from './staffhider';
|
||||||
import { pollOptionCreator } from './poll';
|
import { pollOptionCreator } from './poll';
|
||||||
|
import { warnAboutPMs } from './pmwarning';
|
||||||
|
|
||||||
whenReady(() => {
|
whenReady(() => {
|
||||||
|
|
||||||
|
@ -66,5 +67,6 @@ whenReady(() => {
|
||||||
setupToolbar();
|
setupToolbar();
|
||||||
hideStaffTools();
|
hideStaffTools();
|
||||||
pollOptionCreator();
|
pollOptionCreator();
|
||||||
|
warnAboutPMs();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Philomena.Comments do
|
||||||
|
|
||||||
alias Philomena.Elasticsearch
|
alias Philomena.Elasticsearch
|
||||||
alias Philomena.Reports.Report
|
alias Philomena.Reports.Report
|
||||||
|
alias Philomena.UserStatistics
|
||||||
alias Philomena.Comments.Comment
|
alias Philomena.Comments.Comment
|
||||||
alias Philomena.Comments.ElasticsearchIndex, as: CommentIndex
|
alias Philomena.Comments.ElasticsearchIndex, as: CommentIndex
|
||||||
alias Philomena.IndexWorker
|
alias Philomena.IndexWorker
|
||||||
|
@ -197,6 +198,44 @@ defmodule Philomena.Comments do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def approve_comment(%Comment{} = comment, user) do
|
||||||
|
reports =
|
||||||
|
Report
|
||||||
|
|> where(reportable_type: "Comment", reportable_id: ^comment.id)
|
||||||
|
|> select([r], r.id)
|
||||||
|
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
|
||||||
|
|
||||||
|
comment = Comment.approve_changeset(comment)
|
||||||
|
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.update(:comment, comment)
|
||||||
|
|> Multi.update_all(:reports, reports, [])
|
||||||
|
|> 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)
|
||||||
|
|
||||||
|
{:ok, comment}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_non_approved(%Comment{approved: true}), do: false
|
||||||
|
|
||||||
|
def report_non_approved(comment) do
|
||||||
|
Reports.create_system_report(
|
||||||
|
comment.id,
|
||||||
|
"Comment",
|
||||||
|
"Approval",
|
||||||
|
"Comment contains externally-embedded images and has been flagged for review."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def migrate_comments(image, duplicate_of_image) do
|
def migrate_comments(image, duplicate_of_image) do
|
||||||
{count, nil} =
|
{count, nil} =
|
||||||
Comment
|
Comment
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Philomena.Comments.Comment do
|
||||||
|
|
||||||
alias Philomena.Images.Image
|
alias Philomena.Images.Image
|
||||||
alias Philomena.Users.User
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Schema.Approval
|
||||||
|
|
||||||
schema "comments" do
|
schema "comments" do
|
||||||
belongs_to :user, User
|
belongs_to :user, User
|
||||||
|
@ -22,6 +23,7 @@ defmodule Philomena.Comments.Comment do
|
||||||
field :deletion_reason, :string, default: ""
|
field :deletion_reason, :string, default: ""
|
||||||
field :destroyed_content, :boolean, default: false
|
field :destroyed_content, :boolean, default: false
|
||||||
field :name_at_post_time, :string
|
field :name_at_post_time, :string
|
||||||
|
field :approved, :boolean
|
||||||
|
|
||||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
@ -34,6 +36,8 @@ defmodule Philomena.Comments.Comment do
|
||||||
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
||||||
|> change(attribution)
|
|> change(attribution)
|
||||||
|> put_name_at_post_time(attribution[:user])
|
|> put_name_at_post_time(attribution[:user])
|
||||||
|
|> Approval.maybe_put_approval(attribution[:user])
|
||||||
|
|> Approval.maybe_strip_images(attribution[:user])
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(comment, attrs, edited_at \\ nil) do
|
def changeset(comment, attrs, edited_at \\ nil) do
|
||||||
|
@ -43,6 +47,7 @@ defmodule Philomena.Comments.Comment do
|
||||||
|> validate_required([:body])
|
|> validate_required([:body])
|
||||||
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
||||||
|> validate_length(:edit_reason, max: 70, count: :bytes)
|
|> validate_length(:edit_reason, max: 70, count: :bytes)
|
||||||
|
|> Approval.maybe_put_approval(comment.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide_changeset(comment, attrs, user) do
|
def hide_changeset(comment, attrs, user) do
|
||||||
|
@ -65,6 +70,11 @@ defmodule Philomena.Comments.Comment do
|
||||||
|> put_change(:body, "")
|
|> put_change(:body, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def approve_changeset(comment) do
|
||||||
|
change(comment)
|
||||||
|
|> put_change(:approved, true)
|
||||||
|
end
|
||||||
|
|
||||||
defp put_name_at_post_time(changeset, nil), do: changeset
|
defp put_name_at_post_time(changeset, nil), do: changeset
|
||||||
defp put_name_at_post_time(changeset, user), do: change(changeset, name_at_post_time: user.name)
|
defp put_name_at_post_time(changeset, user), do: change(changeset, name_at_post_time: user.name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -30,7 +30,8 @@ defmodule Philomena.Comments.ElasticsearchIndex do
|
||||||
anonymous: %{type: "keyword"},
|
anonymous: %{type: "keyword"},
|
||||||
# boolean
|
# boolean
|
||||||
hidden_from_users: %{type: "keyword"},
|
hidden_from_users: %{type: "keyword"},
|
||||||
body: %{type: "text", analyzer: "snowball"}
|
body: %{type: "text", analyzer: "snowball"},
|
||||||
|
approved: %{type: "boolean"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +50,8 @@ defmodule Philomena.Comments.ElasticsearchIndex do
|
||||||
image_tag_ids: comment.image.tags |> Enum.map(& &1.id),
|
image_tag_ids: comment.image.tags |> Enum.map(& &1.id),
|
||||||
anonymous: comment.anonymous,
|
anonymous: comment.anonymous,
|
||||||
hidden_from_users: comment.image.hidden_from_users || comment.hidden_from_users,
|
hidden_from_users: comment.image.hidden_from_users || comment.hidden_from_users,
|
||||||
body: comment.body
|
body: comment.body,
|
||||||
|
approved: comment.image.approved && comment.approved
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ defmodule Philomena.Conversations do
|
||||||
import Ecto.Query, warn: false
|
import Ecto.Query, warn: false
|
||||||
alias Ecto.Multi
|
alias Ecto.Multi
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
|
alias Philomena.Reports
|
||||||
|
alias Philomena.Reports.Report
|
||||||
alias Philomena.Conversations.Conversation
|
alias Philomena.Conversations.Conversation
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -187,6 +188,12 @@ defmodule Philomena.Conversations do
|
||||||
Ecto.build_assoc(conversation, :messages)
|
Ecto.build_assoc(conversation, :messages)
|
||||||
|> Message.creation_changeset(attrs, user)
|
|> Message.creation_changeset(attrs, user)
|
||||||
|
|
||||||
|
show_as_read =
|
||||||
|
case message do
|
||||||
|
%{changes: %{approved: true}} -> false
|
||||||
|
_ -> true
|
||||||
|
end
|
||||||
|
|
||||||
conversation_query =
|
conversation_query =
|
||||||
Conversation
|
Conversation
|
||||||
|> where(id: ^conversation.id)
|
|> where(id: ^conversation.id)
|
||||||
|
@ -196,11 +203,57 @@ defmodule Philomena.Conversations do
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.insert(:message, message)
|
|> Multi.insert(:message, message)
|
||||||
|> Multi.update_all(:conversation, conversation_query,
|
|> 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()
|
|> Repo.transaction()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def approve_conversation_message(message, user) do
|
||||||
|
reports_query =
|
||||||
|
Report
|
||||||
|
|> where(reportable_type: "Conversation", reportable_id: ^message.conversation_id)
|
||||||
|
|> select([r], r.id)
|
||||||
|
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
|
||||||
|
|
||||||
|
message_query =
|
||||||
|
message
|
||||||
|
|> Message.approve_changeset()
|
||||||
|
|
||||||
|
conversation_query =
|
||||||
|
Conversation
|
||||||
|
|> where(id: ^message.conversation_id)
|
||||||
|
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.update(:message, message_query)
|
||||||
|
|> Multi.update_all(:conversation, conversation_query, set: [to_read: false])
|
||||||
|
|> Multi.update_all(:reports, reports_query, [])
|
||||||
|
|> Repo.transaction()
|
||||||
|
|> case do
|
||||||
|
{:ok, %{reports: {_count, reports}} = result} ->
|
||||||
|
Reports.reindex_reports(reports)
|
||||||
|
|
||||||
|
{:ok, result}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
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 """
|
@doc """
|
||||||
Updates a message.
|
Updates a message.
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,11 @@ defmodule Philomena.Conversations.Conversation do
|
||||||
|> cast(attrs, [:from_read, :to_read])
|
|> cast(attrs, [:from_read, :to_read])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_read_changeset(conversation) do
|
||||||
|
change(conversation)
|
||||||
|
|> put_change(:to_read, true)
|
||||||
|
end
|
||||||
|
|
||||||
def hidden_changeset(conversation, attrs) do
|
def hidden_changeset(conversation, attrs) do
|
||||||
conversation
|
conversation
|
||||||
|> cast(attrs, [:from_hidden, :to_hidden])
|
|> cast(attrs, [:from_hidden, :to_hidden])
|
||||||
|
|
|
@ -4,12 +4,14 @@ defmodule Philomena.Conversations.Message do
|
||||||
|
|
||||||
alias Philomena.Conversations.Conversation
|
alias Philomena.Conversations.Conversation
|
||||||
alias Philomena.Users.User
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Schema.Approval
|
||||||
|
|
||||||
schema "messages" do
|
schema "messages" do
|
||||||
belongs_to :conversation, Conversation
|
belongs_to :conversation, Conversation
|
||||||
belongs_to :from, User
|
belongs_to :from, User
|
||||||
|
|
||||||
field :body, :string
|
field :body, :string
|
||||||
|
field :approved, :boolean, default: false
|
||||||
|
|
||||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
@ -28,5 +30,10 @@ defmodule Philomena.Conversations.Message do
|
||||||
|> validate_required([:body])
|
|> validate_required([:body])
|
||||||
|> put_assoc(:from, user)
|
|> put_assoc(:from, user)
|
||||||
|> validate_length(:body, max: 300_000, count: :bytes)
|
|> 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
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,7 +115,7 @@ defmodule Philomena.Images do
|
||||||
repair_image(image)
|
repair_image(image)
|
||||||
reindex_image(image)
|
reindex_image(image)
|
||||||
Tags.reindex_tags(image.added_tags)
|
Tags.reindex_tags(image.added_tags)
|
||||||
UserStatistics.inc_stat(attribution[:user], :uploads)
|
maybe_approve_image(image, attribution[:user])
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
||||||
|
@ -135,6 +135,55 @@ defmodule Philomena.Images do
|
||||||
multi
|
multi
|
||||||
end
|
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
|
def feature_image(featurer, %Image{} = image) do
|
||||||
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
||||||
|> ImageFeature.changeset(%{})
|
|> ImageFeature.changeset(%{})
|
||||||
|
|
|
@ -86,7 +86,8 @@ defmodule Philomena.Images.ElasticsearchIndex do
|
||||||
name_in_namespace: %{type: "keyword"},
|
name_in_namespace: %{type: "keyword"},
|
||||||
namespace: %{type: "keyword"}
|
namespace: %{type: "keyword"}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
approved: %{type: "boolean"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +150,8 @@ defmodule Philomena.Images.ElasticsearchIndex do
|
||||||
hidden_by_users: image.hiders |> Enum.map(&String.downcase(&1.name)),
|
hidden_by_users: image.hiders |> Enum.map(&String.downcase(&1.name)),
|
||||||
upvoters: image.upvoters |> Enum.map(&String.downcase(&1.name)),
|
upvoters: image.upvoters |> Enum.map(&String.downcase(&1.name)),
|
||||||
downvoters: image.downvoters |> Enum.map(&String.downcase(&1.name)),
|
downvoters: image.downvoters |> Enum.map(&String.downcase(&1.name)),
|
||||||
deleted_by_user: if(!!image.deleter, do: image.deleter.name)
|
deleted_by_user: if(!!image.deleter, do: image.deleter.name),
|
||||||
|
approved: image.approved
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ defmodule Philomena.Images.Image do
|
||||||
field :hidden_image_key, :string
|
field :hidden_image_key, :string
|
||||||
field :scratchpad, :string
|
field :scratchpad, :string
|
||||||
field :hides_count, :integer, default: 0
|
field :hides_count, :integer, default: 0
|
||||||
|
field :approved, :boolean
|
||||||
|
|
||||||
# todo: can probably remove these now
|
# todo: can probably remove these now
|
||||||
field :tag_list_cache, :string
|
field :tag_list_cache, :string
|
||||||
|
@ -325,6 +326,12 @@ defmodule Philomena.Images.Image do
|
||||||
cast(image, attrs, [:anonymous])
|
cast(image, attrs, [:anonymous])
|
||||||
end
|
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
|
def cache_changeset(image) do
|
||||||
changeset = change(image)
|
changeset = change(image)
|
||||||
image = apply_changes(changeset)
|
image = apply_changes(changeset)
|
||||||
|
|
|
@ -10,6 +10,7 @@ defmodule Philomena.Posts do
|
||||||
alias Philomena.Elasticsearch
|
alias Philomena.Elasticsearch
|
||||||
alias Philomena.Topics.Topic
|
alias Philomena.Topics.Topic
|
||||||
alias Philomena.Topics
|
alias Philomena.Topics
|
||||||
|
alias Philomena.UserStatistics
|
||||||
alias Philomena.Posts.Post
|
alias Philomena.Posts.Post
|
||||||
alias Philomena.Posts.ElasticsearchIndex, as: PostIndex
|
alias Philomena.Posts.ElasticsearchIndex, as: PostIndex
|
||||||
alias Philomena.IndexWorker
|
alias Philomena.IndexWorker
|
||||||
|
@ -117,6 +118,17 @@ defmodule Philomena.Posts do
|
||||||
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
|
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def report_non_approved(%Post{approved: true}), do: false
|
||||||
|
|
||||||
|
def report_non_approved(post) do
|
||||||
|
Reports.create_system_report(
|
||||||
|
post.id,
|
||||||
|
"Post",
|
||||||
|
"Approval",
|
||||||
|
"Post contains externally-embedded images and has been flagged for review."
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def perform_notify(post_id) do
|
def perform_notify(post_id) do
|
||||||
post = get_post!(post_id)
|
post = get_post!(post_id)
|
||||||
|
|
||||||
|
@ -237,6 +249,33 @@ defmodule Philomena.Posts do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def approve_post(%Post{} = post, user) do
|
||||||
|
reports =
|
||||||
|
Report
|
||||||
|
|> where(reportable_type: "Post", reportable_id: ^post.id)
|
||||||
|
|> select([r], r.id)
|
||||||
|
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
|
||||||
|
|
||||||
|
post = Post.approve_changeset(post)
|
||||||
|
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.update(:post, post)
|
||||||
|
|> Multi.update_all(:reports, reports, [])
|
||||||
|
|> 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)
|
||||||
|
|
||||||
|
{:ok, post}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking post changes.
|
Returns an `%Ecto.Changeset{}` for tracking post changes.
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ defmodule Philomena.Posts.ElasticsearchIndex do
|
||||||
created_at: %{type: "date"},
|
created_at: %{type: "date"},
|
||||||
deleted: %{type: "boolean"},
|
deleted: %{type: "boolean"},
|
||||||
access_level: %{type: "keyword"},
|
access_level: %{type: "keyword"},
|
||||||
destroyed_content: %{type: "boolean"}
|
destroyed_content: %{type: "boolean"},
|
||||||
|
approved: %{type: "boolean"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,7 +64,8 @@ defmodule Philomena.Posts.ElasticsearchIndex do
|
||||||
updated_at: post.updated_at,
|
updated_at: post.updated_at,
|
||||||
deleted: post.hidden_from_users,
|
deleted: post.hidden_from_users,
|
||||||
access_level: post.topic.forum.access_level,
|
access_level: post.topic.forum.access_level,
|
||||||
destroyed_content: post.destroyed_content
|
destroyed_content: post.destroyed_content,
|
||||||
|
approved: post.approved
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule Philomena.Posts.Post do
|
||||||
|
|
||||||
alias Philomena.Users.User
|
alias Philomena.Users.User
|
||||||
alias Philomena.Topics.Topic
|
alias Philomena.Topics.Topic
|
||||||
|
alias Philomena.Schema.Approval
|
||||||
|
|
||||||
schema "posts" do
|
schema "posts" do
|
||||||
belongs_to :user, User
|
belongs_to :user, User
|
||||||
|
@ -23,6 +24,7 @@ defmodule Philomena.Posts.Post do
|
||||||
field :deletion_reason, :string, default: ""
|
field :deletion_reason, :string, default: ""
|
||||||
field :destroyed_content, :boolean, default: false
|
field :destroyed_content, :boolean, default: false
|
||||||
field :name_at_post_time, :string
|
field :name_at_post_time, :string
|
||||||
|
field :approved, :boolean, default: false
|
||||||
|
|
||||||
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
timestamps(inserted_at: :created_at, type: :utc_datetime)
|
||||||
end
|
end
|
||||||
|
@ -35,6 +37,7 @@ defmodule Philomena.Posts.Post do
|
||||||
|> validate_required([:body])
|
|> validate_required([:body])
|
||||||
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
||||||
|> validate_length(:edit_reason, max: 70, count: :bytes)
|
|> validate_length(:edit_reason, max: 70, count: :bytes)
|
||||||
|
|> Approval.maybe_put_approval(post.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -45,6 +48,8 @@ defmodule Philomena.Posts.Post do
|
||||||
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|
||||||
|> change(attribution)
|
|> change(attribution)
|
||||||
|> put_name_at_post_time(attribution[:user])
|
|> put_name_at_post_time(attribution[:user])
|
||||||
|
|> Approval.maybe_put_approval(attribution[:user])
|
||||||
|
|> Approval.maybe_strip_images(attribution[:user])
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc false
|
@doc false
|
||||||
|
@ -57,6 +62,8 @@ defmodule Philomena.Posts.Post do
|
||||||
|> change(attribution)
|
|> change(attribution)
|
||||||
|> change(topic_position: 0)
|
|> change(topic_position: 0)
|
||||||
|> put_name_at_post_time(attribution[:user])
|
|> put_name_at_post_time(attribution[:user])
|
||||||
|
|> Approval.maybe_put_approval(attribution[:user])
|
||||||
|
|> Approval.maybe_strip_images(attribution[:user])
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide_changeset(post, attrs, user) do
|
def hide_changeset(post, attrs, user) do
|
||||||
|
@ -79,6 +86,11 @@ defmodule Philomena.Posts.Post do
|
||||||
|> put_change(:body, "")
|
|> put_change(:body, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def approve_changeset(post) do
|
||||||
|
change(post)
|
||||||
|
|> put_change(:approved, true)
|
||||||
|
end
|
||||||
|
|
||||||
defp put_name_at_post_time(changeset, nil), do: changeset
|
defp put_name_at_post_time(changeset, nil), do: changeset
|
||||||
defp put_name_at_post_time(changeset, user), do: change(changeset, name_at_post_time: user.name)
|
defp put_name_at_post_time(changeset, user), do: change(changeset, name_at_post_time: user.name)
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,6 +60,26 @@ defmodule Philomena.Reports do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_system_report(reportable_id, reportable_type, category, reason) do
|
||||||
|
attrs = %{
|
||||||
|
reason: reason,
|
||||||
|
category: category
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes = %{
|
||||||
|
system: true,
|
||||||
|
ip: %Postgrex.INET{address: {127, 0, 0, 1}, netmask: 32},
|
||||||
|
fingerprint: "ffff",
|
||||||
|
user_agent:
|
||||||
|
"Mozilla/5.0 (X11; Philomena; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
%Report{reportable_id: reportable_id, reportable_type: reportable_type}
|
||||||
|
|> Report.creation_changeset(attrs, attributes)
|
||||||
|
|> Repo.insert()
|
||||||
|
|> reindex_after_update()
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a report.
|
Updates a report.
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ defmodule Philomena.Reports.ElasticsearchIndex do
|
||||||
reportable_type: %{type: "keyword"},
|
reportable_type: %{type: "keyword"},
|
||||||
reportable_id: %{type: "keyword"},
|
reportable_id: %{type: "keyword"},
|
||||||
open: %{type: "boolean"},
|
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,
|
reportable_id: report.reportable_id,
|
||||||
fingerprint: report.fingerprint,
|
fingerprint: report.fingerprint,
|
||||||
open: report.open,
|
open: report.open,
|
||||||
reason: report.reason
|
reason: report.reason,
|
||||||
|
system: report.system
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Philomena.Reports.Report do
|
||||||
field :reason, :string
|
field :reason, :string
|
||||||
field :state, :string, default: "open"
|
field :state, :string, default: "open"
|
||||||
field :open, :boolean, default: true
|
field :open, :boolean, default: true
|
||||||
|
field :system, :boolean, default: false
|
||||||
|
|
||||||
# fixme: rails polymorphic relation
|
# fixme: rails polymorphic relation
|
||||||
field :reportable_id, :integer
|
field :reportable_id, :integer
|
||||||
|
|
45
lib/philomena/schema/approval.ex
Normal file
45
lib/philomena/schema/approval.ex
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
defmodule Philomena.Schema.Approval do
|
||||||
|
alias Philomena.Users.User
|
||||||
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@image_embed_regex ~r/!+\[/
|
||||||
|
|
||||||
|
def maybe_put_approval(changeset, nil),
|
||||||
|
do: change(changeset, approved: true)
|
||||||
|
|
||||||
|
def maybe_put_approval(changeset, %{role: role})
|
||||||
|
when role != "user",
|
||||||
|
do: change(changeset, approved: true)
|
||||||
|
|
||||||
|
def maybe_put_approval(
|
||||||
|
%{changes: %{body: body}, valid?: true} = changeset,
|
||||||
|
%User{} = user
|
||||||
|
) do
|
||||||
|
now = now_time()
|
||||||
|
# 14 * 24 * 60 * 60
|
||||||
|
two_weeks = 1_209_600
|
||||||
|
|
||||||
|
case String.match?(body, @image_embed_regex) do
|
||||||
|
true ->
|
||||||
|
case DateTime.compare(now, DateTime.add(user.created_at, two_weeks)) do
|
||||||
|
:gt -> change(changeset, approved: true)
|
||||||
|
_ -> change(changeset, approved: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
change(changeset, approved: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def maybe_put_approval(changeset, _user), do: changeset
|
||||||
|
|
||||||
|
def maybe_strip_images(
|
||||||
|
%{changes: %{body: body}, valid?: true} = changeset,
|
||||||
|
nil
|
||||||
|
),
|
||||||
|
do: change(changeset, body: Regex.replace(@image_embed_regex, body, "["))
|
||||||
|
|
||||||
|
def maybe_strip_images(changeset, _user), do: changeset
|
||||||
|
|
||||||
|
defp now_time(), do: DateTime.truncate(DateTime.utc_now(), :second)
|
||||||
|
end
|
|
@ -75,6 +75,7 @@ defmodule Philomena.Topics do
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{topic: topic}} = result ->
|
{:ok, %{topic: topic}} = result ->
|
||||||
Posts.reindex_post(hd(topic.posts))
|
Posts.reindex_post(hd(topic.posts))
|
||||||
|
Posts.report_non_approved(hd(topic.posts))
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
||||||
|
|
|
@ -671,6 +671,18 @@ defmodule Philomena.Users do
|
||||||
|> setup_roles()
|
|> setup_roles()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def verify_user(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> User.verify_changeset()
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
def unverify_user(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> User.unverify_changeset()
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
defp setup_roles(nil), do: nil
|
defp setup_roles(nil), do: nil
|
||||||
|
|
||||||
defp setup_roles(user) do
|
defp setup_roles(user) do
|
||||||
|
|
|
@ -45,8 +45,14 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
# View filters
|
# View filters
|
||||||
def can?(%User{role: "moderator"}, :show, %Filter{}), do: true
|
def can?(%User{role: "moderator"}, :show, %Filter{}), do: true
|
||||||
|
|
||||||
# Manage images
|
# Privileged mods can hard-delete images
|
||||||
|
def can?(%User{role: "moderator", role_map: %{"Image" => "admin"}}, :destroy, %Image{}),
|
||||||
|
do: true
|
||||||
|
|
||||||
|
# ...but normal ones cannot
|
||||||
def can?(%User{role: "moderator"}, :destroy, %Image{}), do: false
|
def can?(%User{role: "moderator"}, :destroy, %Image{}), do: false
|
||||||
|
|
||||||
|
# Manage images
|
||||||
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
|
||||||
|
|
||||||
|
@ -62,8 +68,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
|
|
||||||
def can?(%User{role: "moderator"}, :show, %Topic{hidden_from_users: true}), do: true
|
def can?(%User{role: "moderator"}, :show, %Topic{hidden_from_users: true}), do: true
|
||||||
|
|
||||||
# View conversations
|
# View and approve conversations
|
||||||
def can?(%User{role: "moderator"}, :show, %Conversation{}), do: true
|
def can?(%User{role: "moderator"}, :show, %Conversation{}), do: true
|
||||||
|
def can?(%User{role: "moderator"}, :approve, %Conversation{}), do: true
|
||||||
|
|
||||||
# View IP addresses and fingerprints
|
# View IP addresses and fingerprints
|
||||||
def can?(%User{role: "moderator"}, :show, :ip_address), do: true
|
def can?(%User{role: "moderator"}, :show, :ip_address), do: true
|
||||||
|
@ -90,9 +97,11 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
def can?(%User{role: "moderator"}, :edit, %Post{}), do: true
|
def can?(%User{role: "moderator"}, :edit, %Post{}), do: true
|
||||||
def can?(%User{role: "moderator"}, :hide, %Post{}), do: true
|
def can?(%User{role: "moderator"}, :hide, %Post{}), do: true
|
||||||
def can?(%User{role: "moderator"}, :delete, %Post{}), do: true
|
def can?(%User{role: "moderator"}, :delete, %Post{}), do: true
|
||||||
|
def can?(%User{role: "moderator"}, :approve, %Post{}), do: true
|
||||||
def can?(%User{role: "moderator"}, :edit, %Comment{}), do: true
|
def can?(%User{role: "moderator"}, :edit, %Comment{}), do: true
|
||||||
def can?(%User{role: "moderator"}, :hide, %Comment{}), do: true
|
def can?(%User{role: "moderator"}, :hide, %Comment{}), do: true
|
||||||
def can?(%User{role: "moderator"}, :delete, %Comment{}), do: true
|
def can?(%User{role: "moderator"}, :delete, %Comment{}), do: true
|
||||||
|
def can?(%User{role: "moderator"}, :approve, %Comment{}), do: true
|
||||||
|
|
||||||
# Show the DNP list
|
# Show the DNP list
|
||||||
def can?(%User{role: "moderator"}, _action, DnpEntry), do: true
|
def can?(%User{role: "moderator"}, _action, DnpEntry), do: true
|
||||||
|
@ -198,6 +207,13 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
),
|
),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
|
def can?(
|
||||||
|
%User{role: "assistant", role_map: %{"Image" => "moderator"}},
|
||||||
|
:approve,
|
||||||
|
%Image{}
|
||||||
|
),
|
||||||
|
do: true
|
||||||
|
|
||||||
# Dupe assistant actions
|
# Dupe assistant actions
|
||||||
def can?(
|
def can?(
|
||||||
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
|
%User{role: "assistant", role_map: %{"DuplicateReport" => "moderator"}},
|
||||||
|
@ -244,6 +260,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :hide, %Comment{}),
|
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :hide, %Comment{}),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
|
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :approve, %Comment{}),
|
||||||
|
do: true
|
||||||
|
|
||||||
# Topic assistant actions
|
# Topic assistant actions
|
||||||
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Topic{}),
|
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Topic{}),
|
||||||
do: true
|
do: true
|
||||||
|
@ -263,6 +282,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||||
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Post{}),
|
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Post{}),
|
||||||
do: true
|
do: true
|
||||||
|
|
||||||
|
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :approve, %Post{}),
|
||||||
|
do: true
|
||||||
|
|
||||||
# Tag assistant actions
|
# Tag assistant actions
|
||||||
def can?(%User{role: "assistant", role_map: %{"Tag" => "moderator"}}, :edit, %Tag{}), do: true
|
def can?(%User{role: "assistant", role_map: %{"Tag" => "moderator"}}, :edit, %Tag{}), do: true
|
||||||
|
|
||||||
|
|
|
@ -119,6 +119,7 @@ defmodule Philomena.Users.User do
|
||||||
field :hide_default_role, :boolean, default: false
|
field :hide_default_role, :boolean, default: false
|
||||||
field :senior_staff, :boolean, default: false
|
field :senior_staff, :boolean, default: false
|
||||||
field :bypass_rate_limits, :boolean, default: false
|
field :bypass_rate_limits, :boolean, default: false
|
||||||
|
field :verified, :boolean, default: false
|
||||||
|
|
||||||
# For avatar validation/persistence
|
# For avatar validation/persistence
|
||||||
field :avatar_width, :integer, virtual: true
|
field :avatar_width, :integer, virtual: true
|
||||||
|
@ -446,6 +447,14 @@ defmodule Philomena.Users.User do
|
||||||
change(user, forced_filter_id: nil)
|
change(user, forced_filter_id: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def verify_changeset(user) do
|
||||||
|
change(user, verified: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unverify_changeset(user) do
|
||||||
|
change(user, verified: false)
|
||||||
|
end
|
||||||
|
|
||||||
def create_totp_secret_changeset(user) do
|
def create_totp_secret_changeset(user) do
|
||||||
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
|
secret = :crypto.strong_rand_bytes(15) |> Base.encode32()
|
||||||
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
data = Philomena.Users.Encryptor.encrypt_model(secret)
|
||||||
|
|
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(asc: :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: %{
|
bool: %{
|
||||||
must: %{term: %{open: true}},
|
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()
|
|> Repo.all()
|
||||||
|> Polymorphic.load_polymorphic(reportable: [reportable_id: :reportable_type])
|
|> 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",
|
render(conn, "index.html",
|
||||||
title: "Admin - Reports",
|
title: "Admin - Reports",
|
||||||
layout_class: "layout--wide",
|
layout_class: "layout--wide",
|
||||||
reports: reports,
|
reports: reports,
|
||||||
my_reports: my_reports
|
my_reports: my_reports,
|
||||||
|
system_reports: system_reports
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
defmodule PhilomenaWeb.Admin.User.VerificationController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Users.User
|
||||||
|
alias Philomena.Users
|
||||||
|
|
||||||
|
plug :verify_authorized
|
||||||
|
plug :load_resource, model: User, id_name: "user_id", id_field: "slug", persisted: true
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
{:ok, user} = Users.verify_user(conn.assigns.user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "User verification granted.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: user)
|
||||||
|
|> redirect(to: Routes.profile_path(conn, :show, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, _params) do
|
||||||
|
{:ok, user} = Users.unverify_user(conn.assigns.user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "User verification revoked.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: user)
|
||||||
|
|> redirect(to: Routes.profile_path(conn, :show, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp verify_authorized(conn, _opts) do
|
||||||
|
case Canada.Can.can?(conn.assigns.current_user, :index, User) do
|
||||||
|
true -> conn
|
||||||
|
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp log_details(conn, action, user) do
|
||||||
|
body =
|
||||||
|
case action do
|
||||||
|
:create -> "Granted verification to #{user.name}"
|
||||||
|
:delete -> "Revoked verification from #{user.name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
%{body: body, subject_path: Routes.profile_path(conn, :show, user)}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,33 @@
|
||||||
|
defmodule PhilomenaWeb.Conversation.Message.ApproveController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Conversations.Message
|
||||||
|
alias Philomena.Conversations
|
||||||
|
|
||||||
|
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||||
|
|
||||||
|
plug :load_and_authorize_resource,
|
||||||
|
model: Message,
|
||||||
|
id_name: "message_id",
|
||||||
|
persisted: true,
|
||||||
|
preload: [:conversation]
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
message = conn.assigns.message
|
||||||
|
|
||||||
|
{:ok, _message} =
|
||||||
|
Conversations.approve_conversation_message(message, conn.assigns.current_user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Conversation message approved.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: message)
|
||||||
|
|> redirect(to: "/")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp log_details(_conn, _action, message) do
|
||||||
|
%{
|
||||||
|
body: "Approved private message in conversation ##{message.conversation_id}",
|
||||||
|
subject_path: "/"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,7 +20,11 @@ defmodule PhilomenaWeb.Conversation.MessageController do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
case Conversations.create_message(conversation, user, message_params) do
|
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 =
|
count =
|
||||||
Message
|
Message
|
||||||
|> where(conversation_id: ^conversation.id)
|
|> where(conversation_id: ^conversation.id)
|
||||||
|
|
|
@ -107,6 +107,11 @@ defmodule PhilomenaWeb.ConversationController do
|
||||||
|
|
||||||
case Conversations.create_conversation(user, conversation_params) do
|
case Conversations.create_conversation(user, conversation_params) do
|
||||||
{:ok, conversation} ->
|
{:ok, conversation} ->
|
||||||
|
if not hd(conversation.messages).approved do
|
||||||
|
Conversations.report_non_approved(conversation.id)
|
||||||
|
Conversations.set_as_read(conversation)
|
||||||
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:info, "Conversation successfully created.")
|
|> put_flash(:info, "Conversation successfully created.")
|
||||||
|> redirect(to: Routes.conversation_path(conn, :show, conversation))
|
|> redirect(to: Routes.conversation_path(conn, :show, conversation))
|
||||||
|
|
24
lib/philomena_web/controllers/image/approve_controller.ex
Normal file
24
lib/philomena_web/controllers/image/approve_controller.ex
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
defmodule PhilomenaWeb.Image.ApproveController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Images.Image
|
||||||
|
alias Philomena.Images
|
||||||
|
|
||||||
|
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||||
|
plug :load_and_authorize_resource, model: Image, id_name: "image_id", persisted: true
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
image = conn.assigns.image
|
||||||
|
|
||||||
|
{:ok, _comment} = Images.approve_image(image)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Image has been approved.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: image)
|
||||||
|
|> redirect(to: Routes.admin_approval_path(conn, :index))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp log_details(conn, _action, image) do
|
||||||
|
%{body: "Approved image #{image.id}", subject_path: Routes.image_path(conn, :show, image)}
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,35 @@
|
||||||
|
defmodule PhilomenaWeb.Image.Comment.ApproveController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Comments.Comment
|
||||||
|
alias Philomena.Comments
|
||||||
|
alias Philomena.UserStatistics
|
||||||
|
|
||||||
|
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||||
|
|
||||||
|
plug :load_and_authorize_resource,
|
||||||
|
model: Comment,
|
||||||
|
id_name: "comment_id",
|
||||||
|
persisted: true,
|
||||||
|
preload: [:user]
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
comment = conn.assigns.comment
|
||||||
|
|
||||||
|
{:ok, _comment} = Comments.approve_comment(comment, conn.assigns.current_user)
|
||||||
|
|
||||||
|
UserStatistics.inc_stat(comment.user, :comments_posted)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Comment has been approved.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: comment)
|
||||||
|
|> redirect(to: Routes.image_path(conn, :show, comment.image_id) <> "#comment_#{comment.id}")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp log_details(conn, _action, comment) do
|
||||||
|
%{
|
||||||
|
body: "Approved comment on image >>#{comment.image_id}",
|
||||||
|
subject_path: Routes.image_path(conn, :show, comment.image_id) <> "#comment_#{comment.id}"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -78,10 +78,15 @@ defmodule PhilomenaWeb.Image.CommentController do
|
||||||
PhilomenaWeb.Api.Json.CommentView.render("show.json", %{comment: comment})
|
PhilomenaWeb.Api.Json.CommentView.render("show.json", %{comment: comment})
|
||||||
)
|
)
|
||||||
|
|
||||||
Comments.notify_comment(comment)
|
|
||||||
Comments.reindex_comment(comment)
|
Comments.reindex_comment(comment)
|
||||||
Images.reindex_image(conn.assigns.image)
|
Images.reindex_image(conn.assigns.image)
|
||||||
UserStatistics.inc_stat(conn.assigns.current_user, :comments_posted)
|
|
||||||
|
if comment.approved do
|
||||||
|
Comments.notify_comment(comment)
|
||||||
|
UserStatistics.inc_stat(conn.assigns.current_user, :comments_posted)
|
||||||
|
else
|
||||||
|
Comments.report_non_approved(comment)
|
||||||
|
end
|
||||||
|
|
||||||
index(conn, %{"comment_id" => comment.id})
|
index(conn, %{"comment_id" => comment.id})
|
||||||
|
|
||||||
|
@ -107,6 +112,10 @@ defmodule PhilomenaWeb.Image.CommentController do
|
||||||
def update(conn, %{"comment" => comment_params}) do
|
def update(conn, %{"comment" => comment_params}) do
|
||||||
case Comments.update_comment(conn.assigns.comment, conn.assigns.current_user, comment_params) do
|
case Comments.update_comment(conn.assigns.comment, conn.assigns.current_user, comment_params) do
|
||||||
{:ok, %{comment: comment}} ->
|
{:ok, %{comment: comment}} ->
|
||||||
|
if not comment.approved do
|
||||||
|
Comments.report_non_approved(comment)
|
||||||
|
end
|
||||||
|
|
||||||
PhilomenaWeb.Endpoint.broadcast!(
|
PhilomenaWeb.Endpoint.broadcast!(
|
||||||
"firehose",
|
"firehose",
|
||||||
"comment:update",
|
"comment:update",
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
defmodule PhilomenaWeb.Topic.Post.ApproveController do
|
||||||
|
use PhilomenaWeb, :controller
|
||||||
|
|
||||||
|
alias Philomena.Posts.Post
|
||||||
|
alias Philomena.Posts
|
||||||
|
|
||||||
|
plug PhilomenaWeb.CanaryMapPlug, create: :approve
|
||||||
|
|
||||||
|
plug :load_and_authorize_resource,
|
||||||
|
model: Post,
|
||||||
|
id_name: "post_id",
|
||||||
|
persisted: true,
|
||||||
|
preload: [:topic, :user, topic: :forum]
|
||||||
|
|
||||||
|
def create(conn, _params) do
|
||||||
|
post = conn.assigns.post
|
||||||
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
|
case Posts.approve_post(post, user) do
|
||||||
|
{:ok, post} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "Post successfully approved.")
|
||||||
|
|> moderation_log(details: &log_details/3, data: post)
|
||||||
|
|> redirect(
|
||||||
|
to:
|
||||||
|
Routes.forum_topic_path(conn, :show, post.topic.forum, post.topic, post_id: post.id) <>
|
||||||
|
"#post_#{post.id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
{:error, _changeset} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:error, "Unable to approve post!")
|
||||||
|
|> redirect(
|
||||||
|
to:
|
||||||
|
Routes.forum_topic_path(conn, :show, post.topic.forum, post.topic, post_id: post.id) <>
|
||||||
|
"#post_#{post.id}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp log_details(conn, _action, post) do
|
||||||
|
%{
|
||||||
|
body: "Approved forum post ##{post.id} in topic '#{post.topic.title}'",
|
||||||
|
subject_path:
|
||||||
|
Routes.forum_topic_path(conn, :show, post.topic.forum, post.topic, post_id: post.id) <>
|
||||||
|
"#post_#{post.id}"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -35,8 +35,12 @@ defmodule PhilomenaWeb.Topic.PostController do
|
||||||
|
|
||||||
case Posts.create_post(topic, attributes, post_params) do
|
case Posts.create_post(topic, attributes, post_params) do
|
||||||
{:ok, %{post: post}} ->
|
{:ok, %{post: post}} ->
|
||||||
Posts.notify_post(post)
|
if post.approved do
|
||||||
UserStatistics.inc_stat(conn.assigns.current_user, :forum_posts)
|
Posts.notify_post(post)
|
||||||
|
UserStatistics.inc_stat(conn.assigns.current_user, :forum_posts)
|
||||||
|
else
|
||||||
|
Posts.report_non_approved(post)
|
||||||
|
end
|
||||||
|
|
||||||
if forum.access_level == "normal" do
|
if forum.access_level == "normal" do
|
||||||
PhilomenaWeb.Endpoint.broadcast!(
|
PhilomenaWeb.Endpoint.broadcast!(
|
||||||
|
@ -75,7 +79,11 @@ defmodule PhilomenaWeb.Topic.PostController do
|
||||||
user = conn.assigns.current_user
|
user = conn.assigns.current_user
|
||||||
|
|
||||||
case Posts.update_post(post, user, post_params) do
|
case Posts.update_post(post, user, post_params) do
|
||||||
{:ok, _post} ->
|
{:ok, post} ->
|
||||||
|
if not post.approved do
|
||||||
|
Posts.report_non_approved(post)
|
||||||
|
end
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:info, "Post successfully edited.")
|
|> put_flash(:info, "Post successfully edited.")
|
||||||
|> redirect(
|
|> redirect(
|
||||||
|
|
|
@ -63,6 +63,7 @@ defmodule PhilomenaWeb.ImageLoader do
|
||||||
]
|
]
|
||||||
|> maybe_show_deleted(show_hidden?, del)
|
|> maybe_show_deleted(show_hidden?, del)
|
||||||
|> maybe_custom_hide(user, hidden)
|
|> maybe_custom_hide(user, hidden)
|
||||||
|
|> hide_non_approved()
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow moderators to index hidden images
|
# Allow moderators to index hidden images
|
||||||
|
@ -94,6 +95,10 @@ defmodule PhilomenaWeb.ImageLoader do
|
||||||
defp maybe_custom_hide(filters, _user, _param),
|
defp maybe_custom_hide(filters, _user, _param),
|
||||||
do: filters
|
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
|
# 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(%{term: %{"namespaced_tags.name" => tag_name}}), do: [tag_name]
|
||||||
defp search_tag_name(_other_query), do: []
|
defp search_tag_name(_other_query), do: []
|
||||||
|
|
|
@ -75,7 +75,7 @@ defmodule PhilomenaWeb.ImageSorter do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp parse_sf(_params, sd, query) do
|
defp parse_sf(_params, sd, query) do
|
||||||
%{query: query, sorts: [%{"id" => sd}]}
|
%{query: query, sorts: [%{"first_seen_at" => sd}]}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp random_query(seed, sd, query) do
|
defp random_query(seed, sd, query) do
|
||||||
|
|
|
@ -58,6 +58,9 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
image.hidden_from_users ->
|
image.hidden_from_users ->
|
||||||
" (deleted)"
|
" (deleted)"
|
||||||
|
|
||||||
|
not image.approved ->
|
||||||
|
" (pending approval)"
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
""
|
""
|
||||||
end
|
end
|
||||||
|
@ -75,7 +78,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
cond do
|
cond do
|
||||||
img != nil ->
|
img != nil ->
|
||||||
case group do
|
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",
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
image: img,
|
image: img,
|
||||||
size: :medium,
|
size: :medium,
|
||||||
|
@ -83,7 +86,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
)
|
)
|
||||||
|> safe_to_string()
|
|> 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",
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
image: img,
|
image: img,
|
||||||
size: :small,
|
size: :small,
|
||||||
|
@ -91,7 +94,7 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
)
|
)
|
||||||
|> safe_to_string()
|
|> 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",
|
Phoenix.View.render(@image_view, "_image_target.html",
|
||||||
image: img,
|
image: img,
|
||||||
size: :thumb_small,
|
size: :thumb_small,
|
||||||
|
@ -99,6 +102,9 @@ defmodule PhilomenaWeb.MarkdownRenderer do
|
||||||
)
|
)
|
||||||
|> safe_to_string()
|
|> safe_to_string()
|
||||||
|
|
||||||
|
[_id, suffix] when not img.approved ->
|
||||||
|
">>#{img.id}#{suffix}#{link_suffix(img)}"
|
||||||
|
|
||||||
[_id, ""] ->
|
[_id, ""] ->
|
||||||
link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}")
|
link(">>#{img.id}#{link_suffix(img)}", to: "/images/#{img.id}")
|
||||||
|> safe_to_string()
|
|> safe_to_string()
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule PhilomenaWeb.AdminCountersPlug do
|
||||||
alias Philomena.Reports
|
alias Philomena.Reports
|
||||||
alias Philomena.ArtistLinks
|
alias Philomena.ArtistLinks
|
||||||
alias Philomena.DnpEntries
|
alias Philomena.DnpEntries
|
||||||
|
alias Philomena.Images
|
||||||
|
|
||||||
import Plug.Conn, only: [assign: 3]
|
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, false), do: conn
|
||||||
|
|
||||||
defp maybe_assign_admin_metrics(conn, user, true) do
|
defp maybe_assign_admin_metrics(conn, user, true) do
|
||||||
|
pending_approvals = Images.count_pending_approvals()
|
||||||
duplicate_reports = DuplicateReports.count_duplicate_reports(user)
|
duplicate_reports = DuplicateReports.count_duplicate_reports(user)
|
||||||
reports = Reports.count_reports(user)
|
reports = Reports.count_reports(user)
|
||||||
artist_links = ArtistLinks.count_artist_links(user)
|
artist_links = ArtistLinks.count_artist_links(user)
|
||||||
dnps = DnpEntries.count_dnp_entries(user)
|
dnps = DnpEntries.count_dnp_entries(user)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|> assign(:pending_approval_count, pending_approvals)
|
||||||
|> assign(:duplicate_report_count, duplicate_reports)
|
|> assign(:duplicate_report_count, duplicate_reports)
|
||||||
|> assign(:report_count, reports)
|
|> assign(:report_count, reports)
|
||||||
|> assign(:artist_link_count, artist_links)
|
|> assign(:artist_link_count, artist_links)
|
||||||
|
|
|
@ -177,7 +177,13 @@ defmodule PhilomenaWeb.Router do
|
||||||
|
|
||||||
resources "/conversations", ConversationController, only: [:index, :show, :new, :create] do
|
resources "/conversations", ConversationController, only: [:index, :show, :new, :create] do
|
||||||
resources "/reports", Conversation.ReportController, only: [:new, :create]
|
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 "/read", Conversation.ReadController, only: [:create, :delete], singleton: true
|
||||||
resources "/hide", Conversation.HideController, only: [:create, :delete], singleton: true
|
resources "/hide", Conversation.HideController, only: [:create, :delete], singleton: true
|
||||||
end
|
end
|
||||||
|
@ -186,6 +192,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/vote", Image.VoteController, only: [:create, :delete], singleton: true
|
resources "/vote", Image.VoteController, only: [:create, :delete], singleton: true
|
||||||
resources "/fave", Image.FaveController, only: [:create, :delete], singleton: true
|
resources "/fave", Image.FaveController, only: [:create, :delete], singleton: true
|
||||||
resources "/hide", Image.HideController, only: [:create, :delete], singleton: true
|
resources "/hide", Image.HideController, only: [:create, :delete], singleton: true
|
||||||
|
resources "/approve", Image.ApproveController, only: [:create], singleton: true
|
||||||
|
|
||||||
resources "/subscription", Image.SubscriptionController,
|
resources "/subscription", Image.SubscriptionController,
|
||||||
only: [:create, :delete],
|
only: [:create, :delete],
|
||||||
|
@ -196,6 +203,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/comments", Image.CommentController, only: [:edit, :update] do
|
resources "/comments", Image.CommentController, only: [:edit, :update] do
|
||||||
resources "/hide", Image.Comment.HideController, only: [:create, :delete], singleton: true
|
resources "/hide", Image.Comment.HideController, only: [:create, :delete], singleton: true
|
||||||
resources "/delete", Image.Comment.DeleteController, only: [:create], singleton: true
|
resources "/delete", Image.Comment.DeleteController, only: [:create], singleton: true
|
||||||
|
resources "/approve", Image.Comment.ApproveController, only: [:create], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/delete", Image.DeleteController,
|
resources "/delete", Image.DeleteController,
|
||||||
|
@ -241,6 +249,7 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/posts", Topic.PostController, only: [:edit, :update] do
|
resources "/posts", Topic.PostController, only: [:edit, :update] do
|
||||||
resources "/hide", Topic.Post.HideController, only: [:create, :delete], singleton: true
|
resources "/hide", Topic.Post.HideController, only: [:create, :delete], singleton: true
|
||||||
resources "/delete", Topic.Post.DeleteController, only: [:create], singleton: true
|
resources "/delete", Topic.Post.DeleteController, only: [:create], singleton: true
|
||||||
|
resources "/approve", Topic.Post.ApproveController, only: [:create], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
resources "/poll", Topic.PollController, only: [:edit, :update], singleton: true do
|
resources "/poll", Topic.PollController, only: [:edit, :update], singleton: true do
|
||||||
|
@ -335,6 +344,8 @@ defmodule PhilomenaWeb.Router do
|
||||||
resources "/close", Report.CloseController, only: [:create], singleton: true
|
resources "/close", Report.CloseController, only: [:create], singleton: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources "/approvals", ApprovalController, only: [:index]
|
||||||
|
|
||||||
resources "/artist_links", ArtistLinkController, only: [:index] do
|
resources "/artist_links", ArtistLinkController, only: [:index] do
|
||||||
resources "/verification", ArtistLink.VerificationController,
|
resources "/verification", ArtistLink.VerificationController,
|
||||||
only: [:create],
|
only: [:create],
|
||||||
|
@ -379,6 +390,10 @@ defmodule PhilomenaWeb.Router do
|
||||||
only: [:create, :delete],
|
only: [:create, :delete],
|
||||||
singleton: true
|
singleton: true
|
||||||
|
|
||||||
|
resources "/verification", User.VerificationController,
|
||||||
|
only: [:create, :delete],
|
||||||
|
singleton: true
|
||||||
|
|
||||||
resources "/unlock", User.UnlockController, only: [:create], singleton: true
|
resources "/unlock", User.UnlockController, only: [:create], singleton: true
|
||||||
resources "/api_key", User.ApiKeyController, only: [:delete], singleton: true
|
resources "/api_key", User.ApiKeyController, only: [:delete], singleton: true
|
||||||
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
|
resources "/downvotes", User.DownvoteController, only: [:delete], singleton: true
|
||||||
|
|
|
@ -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
|
.block__content
|
||||||
= render PhilomenaWeb.Admin.ReportView, "_reports.html", reports: @my_reports, conn: @conn
|
= 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
|
||||||
.block__header
|
.block__header
|
||||||
span.block__header__title All Reports
|
span.block__header__title All Reports
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
article.block.communication id="comment_#{@comment.id}"
|
article.block.communication id="comment_#{@comment.id}"
|
||||||
|
= if not @comment.approved and not @comment.hidden_from_users and (can?(@conn, :hide, @comment) or @comment.user_id == @conn.assigns.current_user.id) do
|
||||||
|
.block__content
|
||||||
|
.block.block--fixed.block--danger
|
||||||
|
p
|
||||||
|
i.fas.fa-exclamation-triangle>
|
||||||
|
' This comment is pending approval from a staff member.
|
||||||
|
= if can?(@conn, :approve, @comment) do
|
||||||
|
p
|
||||||
|
ul.horizontal-list
|
||||||
|
li
|
||||||
|
= link(to: Routes.image_comment_approve_path(@conn, :create, @comment.image_id, @comment), data: [confirm: "Are you sure?"], method: "post", class: "button") do
|
||||||
|
i.fas.fa-check>
|
||||||
|
' Approve
|
||||||
|
li
|
||||||
|
a.button.togglable-delete-form-link href="#" data-click-toggle="#inline-reject-form-comment-#{@comment.id}"
|
||||||
|
i.fa.fa-times>
|
||||||
|
' Reject
|
||||||
|
|
||||||
|
= form_for :comment, Routes.image_comment_hide_path(@conn, :create, @comment.image_id, @comment), [class: "togglable-delete-form hidden flex", id: "inline-reject-form-comment-#{@comment.id}"], fn f ->
|
||||||
|
= text_input f, :deletion_reason, class: "input input--wide", placeholder: "Deletion Reason", id: "inline-reject-reason-comment-#{@comment.id}", required: true
|
||||||
|
= submit "Delete", class: "button"
|
||||||
|
|
||||||
.block__content.flex.flex--no-wrap class=communication_body_class(@comment)
|
.block__content.flex.flex--no-wrap class=communication_body_class(@comment)
|
||||||
.flex__fixed.spacing-right
|
.flex__fixed.spacing-right
|
||||||
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn
|
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn
|
||||||
|
|
|
@ -5,6 +5,21 @@ h1 New Conversation
|
||||||
' »
|
' »
|
||||||
span.block__header__title 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 ->
|
= form_for @changeset, Routes.conversation_path(@conn, :create), fn f ->
|
||||||
= if @changeset.action do
|
= if @changeset.action do
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
|
|
|
@ -31,6 +31,21 @@ h1 = @conversation.title
|
||||||
.block__header.block__header--light.page__header
|
.block__header.block__header--light.page__header
|
||||||
.page__pagination = pagination
|
.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
|
= cond do
|
||||||
- @conn.assigns.current_ban ->
|
- @conn.assigns.current_ban ->
|
||||||
= render PhilomenaWeb.BanView, "_ban_reason.html", conn: @conn
|
= render PhilomenaWeb.BanView, "_ban_reason.html", conn: @conn
|
||||||
|
|
|
@ -118,10 +118,15 @@
|
||||||
td.danger Different rating tags
|
td.danger Different rating tags
|
||||||
|
|
||||||
tr
|
tr
|
||||||
= if forward_merge?(report) do
|
= cond do
|
||||||
td.warning Target newer
|
- not source_approved?(report) ->
|
||||||
- else
|
td.danger Source is not approved
|
||||||
td.success Target older
|
- not target_approved?(report) ->
|
||||||
|
td.danger Target is not approved
|
||||||
|
- forward_merge?(report) ->
|
||||||
|
td.warning Target newer
|
||||||
|
- true ->
|
||||||
|
td.success Target older
|
||||||
|
|
||||||
.flex.flex--column.grid--dupe-report-list__cell.border-vertical id="report_options_#{report.id}"
|
.flex.flex--column.grid--dupe-report-list__cell.border-vertical id="report_options_#{report.id}"
|
||||||
.dr__status-options class=background_class
|
.dr__status-options class=background_class
|
||||||
|
|
|
@ -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
|
br
|
||||||
.flex.flex--spaced-out
|
.flex.flex--spaced-out
|
||||||
= link "Lock specific tags", to: Routes.image_tag_lock_path(@conn, :show, @image), class: "button"
|
= 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
|
= 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?"]
|
||||||
|
|
|
@ -14,7 +14,7 @@ elixir:
|
||||||
i.fa.fa-sync
|
i.fa.fa-sync
|
||||||
span.hide-mobile<> Refresh
|
span.hide-mobile<> Refresh
|
||||||
|
|
||||||
= for {comment, body} <- @comments, not comment.destroyed_content or (can?(@conn, :show, comment) and not hide_staff_tools?(@conn)) do
|
= for {comment, body} <- @comments, can_view_communication?(@conn, comment) do
|
||||||
= render PhilomenaWeb.CommentView, "_comment.html", comment: comment, body: body, conn: @conn
|
= render PhilomenaWeb.CommentView, "_comment.html", comment: comment, body: body, conn: @conn
|
||||||
|
|
||||||
.block
|
.block
|
||||||
|
|
|
@ -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_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
|
= render PhilomenaWeb.ImageView, "_image_page.html", image: @image, conn: @conn
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,12 @@
|
||||||
i.fa.fa-fw.fa-list-alt>
|
i.fa.fa-fw.fa-list-alt>
|
||||||
' Mod Logs
|
' 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
|
= if @duplicate_report_count do
|
||||||
= link to: Routes.duplicate_report_path(@conn, :index), class: "header__link", title: "Duplicates" do
|
= link to: Routes.duplicate_report_path(@conn, :index), class: "header__link", title: "Duplicates" do
|
||||||
' D
|
' D
|
||||||
|
|
|
@ -1,4 +1,18 @@
|
||||||
article.block.communication
|
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
|
.block__content.flex.flex--no-wrap
|
||||||
.flex__fixed.spacing-right
|
.flex__fixed.spacing-right
|
||||||
= render PhilomenaWeb.UserAttributionView, "_user_avatar.html", object: %{user: @message.from}, conn: @conn, class: "avatar--100px"
|
= render PhilomenaWeb.UserAttributionView, "_user_avatar.html", object: %{user: @message.from}, conn: @conn, class: "avatar--100px"
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
article.block.communication id="post_#{@post.id}"
|
article.block.communication id="post_#{@post.id}"
|
||||||
|
= if not @post.approved and not @post.hidden_from_users and (can?(@conn, :hide, @post) or @post.user_id == @conn.assigns.current_user.id) do
|
||||||
|
.block__content
|
||||||
|
.block.block--fixed.block--danger
|
||||||
|
p
|
||||||
|
i.fas.fa-exclamation-triangle>
|
||||||
|
' This post is pending approval from a staff member.
|
||||||
|
= if can?(@conn, :approve, @post) do
|
||||||
|
p
|
||||||
|
ul.horizontal-list
|
||||||
|
li
|
||||||
|
= link(to: Routes.forum_topic_post_approve_path(@conn, :create, @post.topic.forum, @post.topic, @post), data: [confirm: "Are you sure?"], method: "post", class: "button") do
|
||||||
|
i.fas.fa-check>
|
||||||
|
' Approve
|
||||||
|
li
|
||||||
|
a.button.togglable-delete-form-link href="#" data-click-toggle="#inline-reject-form-post-#{@post.id}"
|
||||||
|
i.fa.fa-times>
|
||||||
|
' Reject
|
||||||
|
|
||||||
|
= form_for :post, Routes.forum_topic_post_hide_path(@conn, :create, @post.topic.forum, @post.topic, @post), [class: "togglable-delete-form hidden flex", id: "inline-reject-form-post-#{@post.id}"], fn f ->
|
||||||
|
= text_input f, :deletion_reason, class: "input input--wide", placeholder: "Deletion Reason", id: "inline-reject-reason-post-#{@post.id}", required: true
|
||||||
|
= submit "Delete", class: "button"
|
||||||
|
|
||||||
.block__content.flex.flex--no-wrap class=communication_body_class(@post)
|
.block__content.flex.flex--no-wrap class=communication_body_class(@post)
|
||||||
.flex__fixed.spacing-right
|
.flex__fixed.spacing-right
|
||||||
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn
|
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn
|
||||||
|
|
|
@ -153,8 +153,19 @@ a.label.label--primary.label--block href="#" data-click-toggle=".js-admin__optio
|
||||||
i.fa.fa-fw.fa-ban
|
i.fa.fa-fw.fa-ban
|
||||||
span.admin__button Ban this sucker
|
span.admin__button Ban this sucker
|
||||||
|
|
||||||
|
ul.profile-admin__options__column
|
||||||
= if can?(@conn, :index, Philomena.Users.User) do
|
= if can?(@conn, :index, Philomena.Users.User) do
|
||||||
li
|
li
|
||||||
= link to: Routes.admin_user_api_key_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
|
= link to: Routes.admin_user_api_key_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
|
||||||
i.fas.fa-fw.fa-key
|
i.fas.fa-fw.fa-key
|
||||||
span.admin__button Reset API key
|
span.admin__button Reset API key
|
||||||
|
|
||||||
|
li
|
||||||
|
= if @user.verified do
|
||||||
|
= link to: Routes.admin_user_verification_path(@conn, :delete, @user), data: [confirm: "Are you really, really sure?", method: "delete"] do
|
||||||
|
i.fas.fa-fw.fa-user-times
|
||||||
|
span.admin__button Revoke Verification
|
||||||
|
- else
|
||||||
|
= link to: Routes.admin_user_verification_path(@conn, :create, @user), data: [confirm: "Are you really, really sure?", method: "create"] do
|
||||||
|
i.fas.fa-fw.fa-user-check
|
||||||
|
span.admin__button Grant Verification
|
||||||
|
|
|
@ -133,9 +133,9 @@ h1 Search
|
||||||
end
|
end
|
||||||
|
|
||||||
sort_fields = [
|
sort_fields = [
|
||||||
|
"Sort by initial post date": :first_seen_at,
|
||||||
"Sort by image ID": :id,
|
"Sort by image ID": :id,
|
||||||
"Sort by last modification date": :updated_at,
|
"Sort by last modification date": :updated_at,
|
||||||
"Sort by initial post date": :first_seen_at,
|
|
||||||
"Sort by aspect ratio": :aspect_ratio,
|
"Sort by aspect ratio": :aspect_ratio,
|
||||||
"Sort by fave count": :faves,
|
"Sort by fave count": :faves,
|
||||||
"Sort by upvotes": :upvotes,
|
"Sort by upvotes": :upvotes,
|
||||||
|
|
|
@ -59,7 +59,7 @@ h1 = @topic.title
|
||||||
/ The actual posts
|
/ The actual posts
|
||||||
.posts-area
|
.posts-area
|
||||||
.post-list
|
.post-list
|
||||||
= for {post, body} <- @posts, (!post.destroyed_content or can?(@conn, :hide, post)) do
|
= for {post, body} <- @posts, can_view_communication?(@conn, post) do
|
||||||
= render PhilomenaWeb.PostView, "_post.html", conn: @conn, post: post, body: body
|
= render PhilomenaWeb.PostView, "_post.html", conn: @conn, post: post, body: body
|
||||||
|
|
||||||
= if @conn.assigns.advert do
|
= if @conn.assigns.advert do
|
||||||
|
|
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
|
|
@ -56,6 +56,7 @@ defmodule PhilomenaWeb.Admin.UserView do
|
||||||
def description("admin", "Badge"), do: "Manage badges"
|
def description("admin", "Badge"), do: "Manage badges"
|
||||||
def description("admin", "Advert"), do: "Manage ads"
|
def description("admin", "Advert"), do: "Manage ads"
|
||||||
def description("admin", "StaticPage"), do: "Manage static pages"
|
def description("admin", "StaticPage"), do: "Manage static pages"
|
||||||
|
def description("admin", "Image"), do: "Hard-delete images"
|
||||||
|
|
||||||
def description(_name, _resource_type), do: "(unknown permission)"
|
def description(_name, _resource_type), do: "(unknown permission)"
|
||||||
|
|
||||||
|
@ -90,7 +91,8 @@ defmodule PhilomenaWeb.Admin.UserView do
|
||||||
["admin", "SiteNotice"],
|
["admin", "SiteNotice"],
|
||||||
["admin", "Badge"],
|
["admin", "Badge"],
|
||||||
["admin", "Advert"],
|
["admin", "Advert"],
|
||||||
["admin", "StaticPage"]
|
["admin", "StaticPage"],
|
||||||
|
["admin", "Image"]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -162,6 +162,21 @@ defmodule PhilomenaWeb.AppView do
|
||||||
def communication_body_class(%{destroyed_content: true}), do: "communication--destroyed"
|
def communication_body_class(%{destroyed_content: true}), do: "communication--destroyed"
|
||||||
def communication_body_class(_communication), do: nil
|
def communication_body_class(_communication), do: nil
|
||||||
|
|
||||||
|
def can_view_communication?(conn, communication) do
|
||||||
|
user_id =
|
||||||
|
case conn.assigns.current_user do
|
||||||
|
nil -> -1
|
||||||
|
user -> user.id
|
||||||
|
end
|
||||||
|
|
||||||
|
cond do
|
||||||
|
can?(conn, :hide, communication) and not hide_staff_tools?(conn) -> true
|
||||||
|
communication.destroyed_content -> false
|
||||||
|
not communication.approved and communication.user_id != user_id -> false
|
||||||
|
true -> true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def hide_staff_tools?(conn),
|
def hide_staff_tools?(conn),
|
||||||
do: conn.cookies["hide_staff_tools"] == "true"
|
do: conn.cookies["hide_staff_tools"] == "true"
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,16 @@ defmodule PhilomenaWeb.DuplicateReportView do
|
||||||
|
|
||||||
def mergeable?(%{image: image, duplicate_of_image: duplicate_of_image} = report) do
|
def mergeable?(%{image: image, duplicate_of_image: duplicate_of_image} = report) do
|
||||||
same_rating_tags?(report) and not image.hidden_from_users and
|
same_rating_tags?(report) and not image.hidden_from_users and
|
||||||
not duplicate_of_image.hidden_from_users
|
not duplicate_of_image.hidden_from_users and image.approved and
|
||||||
|
duplicate_of_image.approved
|
||||||
|
end
|
||||||
|
|
||||||
|
def source_approved?(%{image: image}) do
|
||||||
|
image.approved
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_approved?(%{duplicate_of_image: image}) do
|
||||||
|
image.approved
|
||||||
end
|
end
|
||||||
|
|
||||||
defp artist_tags(%{tags: tags}) do
|
defp artist_tags(%{tags: tags}) do
|
||||||
|
|
41
priv/repo/migrations/20220321173359_add_approval_queue.exs
Normal file
41
priv/repo/migrations/20220321173359_add_approval_queue.exs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
defmodule Philomena.Repo.Migrations.AddApprovalQueue do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table("reports") do
|
||||||
|
add :system, :boolean, default: false
|
||||||
|
end
|
||||||
|
|
||||||
|
alter table("images") do
|
||||||
|
add :approved, :boolean, default: false
|
||||||
|
end
|
||||||
|
|
||||||
|
alter table("comments") do
|
||||||
|
add :approved, :boolean, default: false
|
||||||
|
end
|
||||||
|
|
||||||
|
alter table("posts") do
|
||||||
|
add :approved, :boolean, default: false
|
||||||
|
end
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
execute("update images set approved = true;")
|
||||||
|
execute("update posts set approved = true;")
|
||||||
|
execute("update comments set approved = true;")
|
||||||
|
execute("update messages set approved = true;")
|
||||||
|
execute("update users set verified = true where created_at < '2022-03-01';")
|
||||||
|
end
|
||||||
|
end
|
|
@ -89,7 +89,8 @@
|
||||||
{"name": "batch_update", "resource_type": "Tag"},
|
{"name": "batch_update", "resource_type": "Tag"},
|
||||||
{"name": "moderator", "resource_type": "Topic"},
|
{"name": "moderator", "resource_type": "Topic"},
|
||||||
{"name": "admin", "resource_type": "Advert"},
|
{"name": "admin", "resource_type": "Advert"},
|
||||||
{"name": "admin", "resource_type": "StaticPage"}
|
{"name": "admin", "resource_type": "StaticPage"},
|
||||||
|
{"name": "admin", "resource_type": "Image"}
|
||||||
],
|
],
|
||||||
"pages": []
|
"pages": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,6 +70,7 @@ for image_def <- resources["remote_images"] do
|
||||||
)
|
)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{image: image}} ->
|
{:ok, %{image: image}} ->
|
||||||
|
Images.approve_image(image)
|
||||||
Images.reindex_image(image)
|
Images.reindex_image(image)
|
||||||
Tags.reindex_tags(image.added_tags)
|
Tags.reindex_tags(image.added_tags)
|
||||||
|
|
||||||
|
@ -91,6 +92,7 @@ for comment_body <- resources["comments"] do
|
||||||
)
|
)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{comment: comment}} ->
|
{:ok, %{comment: comment}} ->
|
||||||
|
Comments.approve_comment(comment, pleb)
|
||||||
Comments.reindex_comment(comment)
|
Comments.reindex_comment(comment)
|
||||||
Images.reindex_image(image)
|
Images.reindex_image(image)
|
||||||
|
|
||||||
|
@ -126,6 +128,7 @@ for %{"forum" => forum_name, "topics" => topics} <- resources["forum_posts"] do
|
||||||
)
|
)
|
||||||
|> case do
|
|> case do
|
||||||
{:ok, %{post: post}} ->
|
{:ok, %{post: post}} ->
|
||||||
|
Posts.approve_post(post, pleb)
|
||||||
Posts.reindex_post(post)
|
Posts.reindex_post(post)
|
||||||
|
|
||||||
{:error, :post, changeset, _so_far} ->
|
{:error, :post, changeset, _so_far} ->
|
||||||
|
|
|
@ -282,7 +282,8 @@ CREATE TABLE public.comments (
|
||||||
deletion_reason character varying DEFAULT ''::character varying NOT NULL,
|
deletion_reason character varying DEFAULT ''::character varying NOT NULL,
|
||||||
destroyed_content boolean DEFAULT false,
|
destroyed_content boolean DEFAULT false,
|
||||||
name_at_post_time character varying,
|
name_at_post_time character varying,
|
||||||
body character varying NOT NULL
|
body character varying NOT NULL,
|
||||||
|
approved boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -971,7 +972,8 @@ CREATE TABLE public.images (
|
||||||
hides_count integer DEFAULT 0 NOT NULL,
|
hides_count integer DEFAULT 0 NOT NULL,
|
||||||
image_duration double precision,
|
image_duration double precision,
|
||||||
description character varying DEFAULT ''::character varying NOT NULL,
|
description character varying DEFAULT ''::character varying NOT NULL,
|
||||||
scratchpad character varying
|
scratchpad character varying,
|
||||||
|
approved boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -1005,7 +1007,8 @@ CREATE TABLE public.messages (
|
||||||
updated_at timestamp without time zone NOT NULL,
|
updated_at timestamp without time zone NOT NULL,
|
||||||
from_id integer NOT NULL,
|
from_id integer NOT NULL,
|
||||||
conversation_id integer NOT NULL,
|
conversation_id integer NOT NULL,
|
||||||
body character varying NOT NULL
|
body character varying NOT NULL,
|
||||||
|
approved boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -1258,7 +1261,8 @@ CREATE TABLE public.posts (
|
||||||
deletion_reason character varying DEFAULT ''::character varying NOT NULL,
|
deletion_reason character varying DEFAULT ''::character varying NOT NULL,
|
||||||
destroyed_content boolean DEFAULT false NOT NULL,
|
destroyed_content boolean DEFAULT false NOT NULL,
|
||||||
name_at_post_time character varying,
|
name_at_post_time character varying,
|
||||||
body character varying NOT NULL
|
body character varying NOT NULL,
|
||||||
|
approved boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -1300,7 +1304,8 @@ CREATE TABLE public.reports (
|
||||||
admin_id integer,
|
admin_id integer,
|
||||||
reportable_id integer NOT NULL,
|
reportable_id integer NOT NULL,
|
||||||
reportable_type character varying NOT NULL,
|
reportable_type character varying NOT NULL,
|
||||||
reason character varying NOT NULL
|
reason character varying NOT NULL,
|
||||||
|
system boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -2050,7 +2055,8 @@ CREATE TABLE public.users (
|
||||||
description character varying,
|
description character varying,
|
||||||
scratchpad character varying,
|
scratchpad character varying,
|
||||||
bypass_rate_limits boolean DEFAULT false,
|
bypass_rate_limits boolean DEFAULT false,
|
||||||
scale_large_images character varying(255) DEFAULT 'true'::character varying NOT NULL
|
scale_large_images character varying(255) DEFAULT 'true'::character varying NOT NULL,
|
||||||
|
verified boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
@ -2897,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);
|
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: -
|
-- Name: index_adverts_on_restrictions; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -4094,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);
|
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: -
|
-- Name: user_tokens_context_token_index; Type: INDEX; Schema: public; Owner: -
|
||||||
--
|
--
|
||||||
|
@ -4970,3 +4990,4 @@ INSERT INTO public."schema_migrations" (version) VALUES (20210921025336);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20210929181319);
|
INSERT INTO public."schema_migrations" (version) VALUES (20210929181319);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20211107130226);
|
INSERT INTO public."schema_migrations" (version) VALUES (20211107130226);
|
||||||
INSERT INTO public."schema_migrations" (version) VALUES (20211219194836);
|
INSERT INTO public."schema_migrations" (version) VALUES (20211219194836);
|
||||||
|
INSERT INTO public."schema_migrations" (version) VALUES (20220321173359);
|
||||||
|
|
Loading…
Reference in a new issue