mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
post and comment approval
This commit is contained in:
parent
a3db6f6eed
commit
eeb2f851e0
32 changed files with 484 additions and 28 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
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.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
|
|
@ -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
|
|
@ -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})
|
||||
|
||||
|
|
45
lib/philomena_web/controllers/topic/approve_controller.ex
Normal file
45
lib/philomena_web/controllers/topic/approve_controller.ex
Normal 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
|
|
@ -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
|
|
@ -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!(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1 +1 @@
|
|||
= render PhilomenaWeb.CommentView, "_comment.html", comment: @comment, body: @body, conn: @conn
|
||||
= render PhilomenaWeb.CommentView, "_comment.html", comment: @comment, body: @body, conn: @conn
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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": []
|
||||
}
|
||||
|
|
|
@ -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} ->
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue