post and comment approval

This commit is contained in:
Luna D 2022-03-22 22:23:30 +01:00 committed by Luna D
parent a3db6f6eed
commit eeb2f851e0
No known key found for this signature in database
GPG key ID: 4B1C63448394F688
32 changed files with 484 additions and 28 deletions

View file

@ -197,6 +197,40 @@ defmodule Philomena.Comments do
|> Repo.update()
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}}} ->
Reports.reindex_reports(reports)
reindex_comment(comment)
{:ok, comment}
error ->
error
end
end
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
{count, nil} =
Comment

View file

@ -4,6 +4,7 @@ defmodule Philomena.Comments.Comment do
alias Philomena.Images.Image
alias Philomena.Users.User
alias Philomena.Schema.Approval
schema "comments" do
belongs_to :user, User
@ -22,7 +23,7 @@ defmodule Philomena.Comments.Comment do
field :deletion_reason, :string, default: ""
field :destroyed_content, :boolean, default: false
field :name_at_post_time, :string
field :approved_at, :utc_datetime
field :approved, :boolean
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
@ -35,6 +36,8 @@ defmodule Philomena.Comments.Comment do
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|> change(attribution)
|> put_name_at_post_time(attribution[:user])
|> Approval.maybe_put_approval(attribution[:user])
|> Approval.maybe_strip_images(attribution[:user])
end
def changeset(comment, attrs, edited_at \\ nil) do
@ -44,6 +47,7 @@ defmodule Philomena.Comments.Comment do
|> validate_required([:body])
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|> validate_length(:edit_reason, max: 70, count: :bytes)
|> Approval.maybe_put_approval(comment.user)
end
def hide_changeset(comment, attrs, user) do
@ -66,6 +70,11 @@ defmodule Philomena.Comments.Comment do
|> put_change(:body, "")
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, user), do: change(changeset, name_at_post_time: user.name)
end

View file

@ -30,7 +30,8 @@ defmodule Philomena.Comments.ElasticsearchIndex do
anonymous: %{type: "keyword"},
# boolean
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),
anonymous: comment.anonymous,
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

View file

@ -4,6 +4,7 @@ defmodule Philomena.Conversations.Message do
alias Philomena.Conversations.Conversation
alias Philomena.Users.User
alias Philomena.Schema.Approval
schema "messages" do
belongs_to :conversation, Conversation
@ -28,5 +29,6 @@ defmodule Philomena.Conversations.Message do
|> validate_required([:body])
|> put_assoc(:from, user)
|> validate_length(:body, max: 300_000, count: :bytes)
|> Approval.maybe_put_approval(user)
end
end

View file

@ -86,7 +86,8 @@ defmodule Philomena.Images.ElasticsearchIndex do
name_in_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)),
upvoters: image.upvoters |> 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

View file

@ -82,7 +82,7 @@ defmodule Philomena.Images.Image do
field :hidden_image_key, :string
field :scratchpad, :string
field :hides_count, :integer, default: 0
field :approved_at, :utc_datetime
field :approved, :boolean
# todo: can probably remove these now
field :tag_list_cache, :string

View file

@ -117,6 +117,15 @@ defmodule Philomena.Posts do
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Posts", post.id])
end
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
post = get_post!(post_id)
@ -237,6 +246,31 @@ defmodule Philomena.Posts do
|> reindex_after_update()
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}}} ->
Reports.reindex_reports(reports)
reindex_post(post)
{:ok, post}
error ->
error
end
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking post changes.

View file

@ -36,7 +36,8 @@ defmodule Philomena.Posts.ElasticsearchIndex do
created_at: %{type: "date"},
deleted: %{type: "boolean"},
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,
deleted: post.hidden_from_users,
access_level: post.topic.forum.access_level,
destroyed_content: post.destroyed_content
destroyed_content: post.destroyed_content,
approved: post.topic.approved && post.approved
}
end

View file

@ -4,6 +4,7 @@ defmodule Philomena.Posts.Post do
alias Philomena.Users.User
alias Philomena.Topics.Topic
alias Philomena.Schema.Approval
schema "posts" do
belongs_to :user, User
@ -23,7 +24,7 @@ defmodule Philomena.Posts.Post do
field :deletion_reason, :string, default: ""
field :destroyed_content, :boolean, default: false
field :name_at_post_time, :string
field :approved_at, :utc_datetime
field :approved, :boolean, default: false
timestamps(inserted_at: :created_at, type: :utc_datetime)
end
@ -36,6 +37,7 @@ defmodule Philomena.Posts.Post do
|> validate_required([:body])
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|> validate_length(:edit_reason, max: 70, count: :bytes)
|> Approval.maybe_put_approval(post.user)
end
@doc false
@ -46,6 +48,8 @@ defmodule Philomena.Posts.Post do
|> validate_length(:body, min: 1, max: 300_000, count: :bytes)
|> change(attribution)
|> put_name_at_post_time(attribution[:user])
|> Approval.maybe_put_approval(attribution[:user])
|> Approval.maybe_strip_images(attribution[:user])
end
@doc false
@ -58,6 +62,8 @@ defmodule Philomena.Posts.Post do
|> change(attribution)
|> change(topic_position: 0)
|> put_name_at_post_time(attribution[:user])
|> Approval.maybe_put_approval(attribution[:user])
|> Approval.maybe_strip_images(attribution[:user])
end
def hide_changeset(post, attrs, user) do
@ -80,6 +86,11 @@ defmodule Philomena.Posts.Post do
|> put_change(:body, "")
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, user), do: change(changeset, name_at_post_time: user.name)
end

View file

@ -60,6 +60,26 @@ defmodule Philomena.Reports do
|> reindex_after_update()
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 """
Updates a report.

View 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

View file

@ -31,7 +31,7 @@ defmodule Philomena.Topics.Topic do
field :slug, :string
field :anonymous, :boolean, default: false
field :hidden_from_users, :boolean, default: false
field :approved_at, :utc_datetime
field :approved, :boolean
timestamps(inserted_at: :created_at, type: :utc_datetime)
end

View file

@ -45,8 +45,14 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
# View filters
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
# Manage images
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
# View conversations
# View and approve conversations
def can?(%User{role: "moderator"}, :show, %Conversation{}), do: true
def can?(%User{role: "moderator"}, :approve, %Conversation{}), do: true
# View IP addresses and fingerprints
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"}, :hide, %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"}, :hide, %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
def can?(%User{role: "moderator"}, _action, DnpEntry), do: true
@ -198,6 +207,13 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
),
do: true
def can?(
%User{role: "assistant", role_map: %{"Image" => "moderator"}},
:approve,
%Image{}
),
do: true
# Dupe assistant actions
def can?(
%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{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Comment" => "moderator"}}, :approve, %Comment{}),
do: true
# Topic assistant actions
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Topic{}),
do: true
@ -254,6 +273,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Topic{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :approve, %Topic{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :show, %Post{}),
do: true
@ -263,6 +285,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :hide, %Post{}),
do: true
def can?(%User{role: "assistant", role_map: %{"Topic" => "moderator"}}, :approve, %Post{}),
do: true
# Tag assistant actions
def can?(%User{role: "assistant", role_map: %{"Tag" => "moderator"}}, :edit, %Tag{}), do: true

View file

@ -0,0 +1,33 @@
defmodule PhilomenaWeb.Conversation.ApproveController do
use PhilomenaWeb, :controller
alias Philomena.Conversations.Conversation
alias Philomena.Conversations
plug PhilomenaWeb.CanaryMapPlug, create: :approve
plug :load_and_authorize_resource,
model: Conversation,
id_field: "slug",
id_name: "conversation_id",
persisted: true
def create(conn, _params) do
message = conn.assigns.message
user = conn.assigns.current_user
{:ok, _message} = Conversations.approve_conversation_message(message)
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

View 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.image_path(conn, :show, image))
end
defp log_details(conn, _action, image) do
%{body: "Approved image #{image.id}", subject_path: Routes.image_path(conn, :show, image)}
end
end

View file

@ -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

View file

@ -81,7 +81,12 @@ defmodule PhilomenaWeb.Image.CommentController do
Comments.notify_comment(comment)
Comments.reindex_comment(comment)
Images.reindex_image(conn.assigns.image)
UserStatistics.inc_stat(conn.assigns.current_user, :comments_posted)
if comment.approved do
UserStatistics.inc_stat(conn.assigns.current_user, :comments_posted)
else
Comments.report_non_approved(comment)
end
index(conn, %{"comment_id" => comment.id})

View file

@ -0,0 +1,45 @@
defmodule PhilomenaWeb.Topic.ApproveController do
import Plug.Conn
use PhilomenaWeb, :controller
alias Philomena.Forums.Forum
alias Philomena.Topics.Topic
alias Philomena.Topics
plug PhilomenaWeb.CanaryMapPlug, create: :show
plug :load_and_authorize_resource,
model: Forum,
id_name: "forum_id",
id_field: "short_name",
persisted: true
plug PhilomenaWeb.LoadTopicPlug
plug PhilomenaWeb.CanaryMapPlug, create: :approve
plug :authorize_resource, model: Topic, persisted: true
def create(conn, _params) do
topic = conn.assigns.topic
user = conn.assigns.current_user
case Topics.approve_topic(topic) do
{:ok, topic} ->
conn
|> put_flash(:info, "Topic successfully approved!")
|> moderation_log(details: &log_details/3, data: topic)
|> redirect(to: Routes.forum_topic_path(conn, :show, topic.forum, topic))
{:error, _changeset} ->
conn
|> put_flash(:error, "Unable to approve the topic!")
|> redirect(to: Routes.forum_topic_path(conn, :show, topic.forum, topic))
end
end
defp log_details(conn, action, topic) do
%{
body: "Approved topic '#{topic.title}' in #{topic.forum.name}",
subject_path: Routes.forum_topic_path(conn, :show, topic.forum, topic)
}
end
end

View file

@ -0,0 +1,51 @@
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} ->
UserStatistics.inc_stat(post.user(:forum_posts))
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

View file

@ -36,7 +36,12 @@ defmodule PhilomenaWeb.Topic.PostController do
case Posts.create_post(topic, attributes, post_params) do
{:ok, %{post: post}} ->
Posts.notify_post(post)
UserStatistics.inc_stat(conn.assigns.current_user, :forum_posts)
if post.approved do
UserStatistics.inc_stat(conn.assigns.current_user, :forum_posts)
else
Posts.report_non_approved(post)
end
if forum.access_level == "normal" do
PhilomenaWeb.Endpoint.broadcast!(

View file

@ -180,12 +180,14 @@ defmodule PhilomenaWeb.Router do
resources "/messages", Conversation.MessageController, only: [:create]
resources "/read", Conversation.ReadController, only: [:create, :delete], singleton: true
resources "/hide", Conversation.HideController, only: [:create, :delete], singleton: true
resources "/approve", Conversation.ApproveController, only: [:create], singleton: true
end
resources "/images", ImageController, only: [] do
resources "/vote", Image.VoteController, only: [:create, :delete], singleton: true
resources "/fave", Image.FaveController, 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,
only: [:create, :delete],
@ -196,6 +198,7 @@ defmodule PhilomenaWeb.Router do
resources "/comments", Image.CommentController, only: [:edit, :update] do
resources "/hide", Image.Comment.HideController, only: [:create, :delete], singleton: true
resources "/delete", Image.Comment.DeleteController, only: [:create], singleton: true
resources "/approve", Image.Comment.ApproveController, only: [:create], singleton: true
end
resources "/delete", Image.DeleteController,
@ -237,10 +240,12 @@ defmodule PhilomenaWeb.Router do
resources "/stick", Topic.StickController, only: [:create, :delete], singleton: true
resources "/lock", Topic.LockController, only: [:create, :delete], singleton: true
resources "/hide", Topic.HideController, only: [:create, :delete], singleton: true
resources "/approve", Topic.ApproveController, only: [:create], singleton: true
resources "/posts", Topic.PostController, only: [:edit, :update] do
resources "/hide", Topic.Post.HideController, only: [:create, :delete], singleton: true
resources "/delete", Topic.Post.DeleteController, only: [:create], singleton: true
resources "/approve", Topic.Post.ApproveController, only: [:create], singleton: true
end
resources "/poll", Topic.PollController, only: [:edit, :update], singleton: true do
@ -379,7 +384,9 @@ defmodule PhilomenaWeb.Router do
only: [:create, :delete],
singleton: true
resources "/verification", User.VerificationController, only: [:create, :delete], singleton: true
resources "/verification", User.VerificationController,
only: [:create, :delete],
singleton: true
resources "/unlock", User.UnlockController, only: [:create], singleton: true
resources "/api_key", User.ApiKeyController, only: [:delete], singleton: true

View file

@ -1,4 +1,26 @@
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)
.flex__fixed.spacing-right
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @comment, conn: @conn

View file

@ -14,7 +14,7 @@ elixir:
i.fa.fa-sync
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
.block

View file

@ -1 +1 @@
= render PhilomenaWeb.CommentView, "_comment.html", comment: @comment, body: @body, conn: @conn
= render PhilomenaWeb.CommentView, "_comment.html", comment: @comment, body: @body, conn: @conn

View file

@ -1,4 +1,26 @@
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)
.flex__fixed.spacing-right
= render PhilomenaWeb.UserAttributionView, "_anon_user_avatar.html", object: @post, conn: @conn

View file

@ -59,7 +59,7 @@ h1 = @topic.title
/ The actual posts
.posts-area
.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
= if @conn.assigns.advert do

View file

@ -56,6 +56,7 @@ defmodule PhilomenaWeb.Admin.UserView do
def description("admin", "Badge"), do: "Manage badges"
def description("admin", "Advert"), do: "Manage ads"
def description("admin", "StaticPage"), do: "Manage static pages"
def description("admin", "Image"), do: "Hard-delete images"
def description(_name, _resource_type), do: "(unknown permission)"
@ -90,7 +91,8 @@ defmodule PhilomenaWeb.Admin.UserView do
["admin", "SiteNotice"],
["admin", "Badge"],
["admin", "Advert"],
["admin", "StaticPage"]
["admin", "StaticPage"],
["admin", "Image"]
]
end
end

View file

@ -162,6 +162,21 @@ defmodule PhilomenaWeb.AppView do
def communication_body_class(%{destroyed_content: true}), do: "communication--destroyed"
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),
do: conn.cookies["hide_staff_tools"] == "true"

View file

@ -7,19 +7,19 @@ defmodule Philomena.Repo.Migrations.AddApprovalQueue do
end
alter table("images") do
add :approved_at, :utc_datetime
add :approved, :boolean, default: false
end
alter table("comments") do
add :approved_at, :utc_datetime
add :approved, :boolean, default: false
end
alter table("posts") do
add :approved_at, :utc_datetime
add :approved, :boolean, default: false
end
alter table("topics") do
add :approved_at, :utc_datetime
add :approved, :boolean, default: false
end
alter table("users") do

View file

@ -89,7 +89,8 @@
{"name": "batch_update", "resource_type": "Tag"},
{"name": "moderator", "resource_type": "Topic"},
{"name": "admin", "resource_type": "Advert"},
{"name": "admin", "resource_type": "StaticPage"}
{"name": "admin", "resource_type": "StaticPage"},
{"name": "admin", "resource_type": "Image"}
],
"pages": []
}

View file

@ -70,6 +70,7 @@ for image_def <- resources["remote_images"] do
)
|> case do
{:ok, %{image: image}} ->
Images.approve_image(image)
Images.reindex_image(image)
Tags.reindex_tags(image.added_tags)
@ -91,6 +92,7 @@ for comment_body <- resources["comments"] do
)
|> case do
{:ok, %{comment: comment}} ->
Comments.approve_comment(comment, pleb)
Comments.reindex_comment(comment)
Images.reindex_image(image)
@ -126,6 +128,7 @@ for %{"forum" => forum_name, "topics" => topics} <- resources["forum_posts"] do
)
|> case do
{:ok, %{post: post}} ->
Posts.approve_post(post, pleb)
Posts.reindex_post(post)
{:error, :post, changeset, _so_far} ->

View file

@ -283,7 +283,7 @@ CREATE TABLE public.comments (
destroyed_content boolean DEFAULT false,
name_at_post_time character varying,
body character varying NOT NULL,
approved_at timestamp(0) without time zone
approved boolean DEFAULT false
);
@ -973,7 +973,7 @@ CREATE TABLE public.images (
image_duration double precision,
description character varying DEFAULT ''::character varying NOT NULL,
scratchpad character varying,
approved_at timestamp(0) without time zone
approved boolean DEFAULT false
);
@ -1261,7 +1261,7 @@ CREATE TABLE public.posts (
destroyed_content boolean DEFAULT false NOT NULL,
name_at_post_time character varying,
body character varying NOT NULL,
approved_at timestamp(0) without time zone
approved boolean DEFAULT false
);
@ -1681,7 +1681,7 @@ CREATE TABLE public.topics (
locked_by_id integer,
last_post_id integer,
hidden_from_users boolean DEFAULT false NOT NULL,
approved_at timestamp(0) without time zone
approved boolean DEFAULT false
);