Added post report feature

You can report a single post by clicking the correct icon in the controler section

Workflow:
* Report a post as offensive
* Trigger alerts to every pod-admin
* Pod-admin can review it in the admin interface
* Delete the post or mark it as reviewed
This commit is contained in:
Lukas Matt 2013-09-12 08:46:59 -04:00
parent 1a8541ff1a
commit 5c9a3aaf3e
21 changed files with 306 additions and 3 deletions

View file

@ -25,7 +25,7 @@
* Collapse aspect list and tag followings list when switching to other views [#4462](https://github.com/diaspora/diaspora/pull/4462)
* Highlight current stream in left sidebar [#4445](https://github.com/diaspora/diaspora/pull/4445)
* Added ignore user icon [#4417](https://github.com/diaspora/diaspora/pull/4417)
* You can report a single post by clicking the correct icon in the controler section [#4517](https://github.com/diaspora/diaspora/pull/4517)
# 0.2.0.0

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

View file

@ -0,0 +1,3 @@
app.models.PostReport = Backbone.Model.extend({
urlRoot: '/post_report'
});

View file

@ -19,6 +19,7 @@ app.views.StreamPost = app.views.Post.extend({
"click .remove_post": "destroyModel",
"click .hide_post": "hidePost",
"click .post_report": "postReport",
"click .block_user": "blockUser"
},
@ -106,6 +107,21 @@ app.views.StreamPost = app.views.Post.extend({
this.remove();
},
postReport : function(evt) {
if(evt) { evt.preventDefault(); }
var text = prompt(Diaspora.I18n.t('post_report_prompt'),
Diaspora.I18n.t('post_report_prompt_default'));
var postReport = new app.models.PostReport();
postReport.fetch({
data: {
post_id: this.model.id,
text: text
},
type: 'POST'
});
},
focusCommentTextarea: function(evt){
evt.preventDefault();
this.$(".new_comment_form_wrapper").removeClass("hidden");

View file

@ -12,6 +12,7 @@
@import 'opengraph'
@import 'help'
@import 'profile'
@import 'post_report'
/* ====== media ====== */
.media
@ -267,6 +268,13 @@ ul.as-selections
:z-index 6
:float right
.post_report
:display inline-block
.icons-postreport
:height 14px
:width 14px
.block_user
:display inline-block

View file

@ -0,0 +1,16 @@
#post_report {
padding-top: 2em;
span {
display: block;
}
.content {
float: left;
}
.options {
float: right;
}
.clear {
clear: both;
padding-bottom: 1em;
}
}

View file

@ -10,6 +10,9 @@
{{#if loggedIn}}
<div class="controls">
{{#unless authorIsCurrentUser}}
<a href="#" rel="nofollow" class="post_report" title="{{t "post_report"}}">
<div class="icons-postreport control_icon"/>
</a>
<a href="#" rel="nofollow" class="block_user" title="{{t "ignore"}}">
<div class="icons-ignoreuser control_icon"></div>
</a>

View file

@ -0,0 +1,61 @@
class PostReportController < ApplicationController
before_filter :authenticate_user!
before_filter :redirect_unless_admin, :except => [:create]
def index
@post_report = PostReport.where(reviewed: false).all
end
def update
if PostReport.exists?(post_id: params[:id])
mark_as_reviewed
end
redirect_to :action => :index and return
end
def destroy
if Post.exists?(params[:id])
delete_post
mark_as_reviewed
end
redirect_to :action => :index and return
end
def create
username = current_user.username
unless PostReport.where(post_id: params[:post_id]).exists?(user_id: username)
post = PostReport.new(
:post_id => params[:post_id],
:user_id => username,
:text => params[:text])
result = post.save
status(( 200 if result ) || ( 422 if !result ))
else
status(409)
end
end
private
def delete_post
post = Post.find(params[:id])
current_user.retract(post)
flash[:notice] = I18n.t 'post_report.status.destroyed'
end
def mark_as_reviewed id = params[:id]
posts = PostReport.where(post_id: id)
posts.each do |post|
post.update_attributes(reviewed: true)
end
flash[:notice] = I18n.t 'post_report.status.marked'
end
def status(code)
if code == 200
flash[:notice] = I18n.t 'post_report.status.created'
else
flash[:error] = I18n.t 'post_report.status.failed'
end
render :nothing => true, :status => code
end
end

View file

@ -0,0 +1,18 @@
class PostReportMailer < ActionMailer::Base
default :from => AppConfig.mail.sender_address
def new_report
Role.admins.each do |role|
email = User.find_by_id(role.person_id).email
format(email).deliver
end
end
private
def format(email)
mail(to: email, subject: I18n.t('notifier.post_report_email.subject')) do |format|
format.text { render 'post_report/post_report_email' }
format.html { render 'post_report/post_report_email' }
end
end
end

15
app/models/post_report.rb Normal file
View file

@ -0,0 +1,15 @@
class PostReport < ActiveRecord::Base
validates :user_id, presence: true
validates :post_id, presence: true
belongs_to :user
belongs_to :post
has_many :post_reports
after_create :send_report_notification
def send_report_notification
Workers::Mail::PostReportWorker.perform_async
end
end

View file

@ -2,6 +2,8 @@
class Role < ActiveRecord::Base
belongs_to :person
scope :admins, -> { where(name: 'admin') }
def self.is_admin?(person)
find_by_person_id_and_name(person.id, 'admin')
end

View file

@ -5,6 +5,7 @@
%li= link_to t('.user_search'), user_search_path
%li= link_to t('.weekly_user_stats'), weekly_user_stats_path
%li= link_to t('.pod_stats'), pod_stats_path
%li= link_to t('.post_report'), post_report_index_path
%li= link_to t('.correlations'), correlations_path
%li= link_to t('.sidekiq_monitor'), sidekiq_path

View file

@ -0,0 +1,21 @@
.span-24
= render :partial => 'admins/admin_bar'
.span-24.last
%h1
= t('post_report.title')
%div#post_report
- @post_report.each do |report|
%div.content
%span
= raw t('post_report.post_label', title: link_to(post_page_title(Post.find_by_id(report.post_id)), post_path(report.post_id)))
%span
= raw t('post_report.reported_label', person: link_to(report.user_id, user_profile_path(report.user_id)))
%span
= t('post_report.reason_label', text: report.text)
%div.options
%span
= link_to t('post_report.review_link'), post_report_path(report.post_id), method: :put
%span
= link_to t('post_report.delete_link'), post_report_path(report.post_id), method: :delete
%div.clear

View file

@ -0,0 +1,2 @@
<%= t('notifier.post_report_email.body') %>

View file

@ -0,0 +1,12 @@
module Workers
module Mail
class PostReportWorker < Base
sidekiq_options queue: :mail
def perform
PostReportMailer.new_report
end
end
end
end

View file

@ -91,6 +91,7 @@ en:
user_search: "User Search"
weekly_user_stats: "Weekly User Stats"
pod_stats: "Pod Stats"
post_report: "Reported Posts"
correlations: "Correlations"
sidekiq_monitor: "Sidekiq monitor"
correlations:
@ -716,6 +717,9 @@ en:
confirm_email:
subject: "Please activate your new email address %{unconfirmed_email}"
click_link: "To activate your new email address %{unconfirmed_email}, please follow this link:"
post_report_email:
subject: "A new post was marked as offensive"
body: "Please review as soon as possible!"
accept_invite: "Accept Your diaspora* invite!"
invited_you: "%{name} invited you to diaspora*"
invite:
@ -845,6 +849,19 @@ en:
other: "%{count} photos by %{author}"
reshare_by: "Reshare by %{author}"
post_report:
title: "Marked Reports Overview"
post_label: "<b>Post</b>: %{title}"
reported_label: "<b>Reported by</b> %{person}"
reason_label: "Reason: %{text}"
review_link: "Mark as reviewed"
delete_link: "Delete post"
status:
marked: "The report was marked as reviewed"
destroyed: "The post was destroyed"
created: "A report was created"
failed: "Something went wrong"
share_visibilites:
update:
post_hidden_and_muted: "%{name}'s post has been hidden, and notifications have been muted."

View file

@ -6,8 +6,11 @@
en:
javascripts:
confirm_dialog: "Are you sure?"
post_report_prompt: "Please enter a reason:"
post_report_prompt_default: "offensive content"
delete: "Delete"
ignore: "Ignore"
post_report: "Report"
ignore_user: "Ignore this user?"
and: "and"
comma: ","

View file

@ -5,6 +5,8 @@
require 'sidekiq/web'
Diaspora::Application.routes.draw do
resources :post_report, :except => [:edit]
if Rails.env.production?
mount RailsAdmin::Engine => '/admin_panel', :as => 'rails_admin'
end

View file

@ -0,0 +1,13 @@
class CreatePostReports < ActiveRecord::Migration
def change
create_table :post_reports do |t|
t.integer :post_id, :null => false
t.string :user_id
t.boolean :reviewed, :default => false
t.text :text
t.timestamps
end
add_index :post_reports, :post_id
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20130801063213) do
ActiveRecord::Schema.define(:version => 20131017093025) do
create_table "account_deletions", :force => true do |t|
t.string "diaspora_handle"
@ -283,6 +283,17 @@ ActiveRecord::Schema.define(:version => 20130801063213) do
t.datetime "updated_at", :null => false
end
create_table "post_reports", :force => true do |t|
t.integer "post_id", :null => false
t.string "user_id"
t.boolean "reviewed", :default => false
t.text "text"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
add_index "post_reports", ["post_id"], :name => "index_post_reports_on_post_id"
create_table "posts", :force => true do |t|
t.integer "author_id", :null => false
t.boolean "public", :default => false, :null => false
@ -316,8 +327,8 @@ ActiveRecord::Schema.define(:version => 20130801063213) do
t.boolean "favorite", :default => false
t.string "facebook_id"
t.string "tweet_id"
t.text "tumblr_ids"
t.integer "open_graph_cache_id"
t.text "tumblr_ids"
end
add_index "posts", ["author_id", "root_guid"], :name => "index_posts_on_author_id_and_root_guid", :unique => true

View file

@ -0,0 +1,79 @@
require 'spec_helper'
describe PostReportController do
before do
sign_in alice
@message = alice.post(:status_message, :text => "hey", :to => alice.aspects.first.id)
end
describe '#index' do
context 'admin not signed in' do
it 'is behind redirect_unless_admin' do
get :index
response.should redirect_to stream_path
end
end
context 'admin signed in' do
before do
Role.add_admin(alice.person)
end
it 'succeeds and renders index' do
get :index
response.should render_template('index')
end
end
end
describe '#create' do
context 'report offensive content' do
it 'succeeds' do
put :create, :post_id => @message.id, :text => 'offensive content'
response.status.should == 200
PostReport.exists?(:post_id => @message.id).should be_true
end
end
end
describe '#update' do
context 'mark report as user' do
it 'is behind redirect_unless_admin' do
put :update, :id => @message.id
response.should redirect_to stream_path
PostReport.where(:reviewed => false, :post_id => @message.id).should be_true
end
end
context 'mark report as admin' do
before do
Role.add_admin(alice.person)
end
it 'succeeds' do
put :update, :id => @message.id
response.status.should == 302
PostReport.where(:reviewed => true, :post_id => @message.id).should be_true
end
end
end
describe '#destroy' do
context 'destroy post as user' do
it 'is behind redirect_unless_admin' do
delete :destroy, :id => @message.id
response.should redirect_to stream_path
PostReport.where(:reviewed => false, :post_id => @message.id).should be_true
end
end
context 'destroy post as admin' do
before do
Role.add_admin(alice.person)
end
it 'succeeds' do
delete :destroy, :id => @message.id
response.status.should == 302
PostReport.where(:reviewed => true, :post_id => @message.id).should be_true
end
end
end
end