diff --git a/Changelog.md b/Changelog.md
index 672a0b6a6..a6f1d8435 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -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
diff --git a/app/assets/images/icons/postreport.png b/app/assets/images/icons/postreport.png
new file mode 100644
index 000000000..9c5967969
Binary files /dev/null and b/app/assets/images/icons/postreport.png differ
diff --git a/app/assets/javascripts/app/models/post_report.js b/app/assets/javascripts/app/models/post_report.js
new file mode 100644
index 000000000..7a9705bb5
--- /dev/null
+++ b/app/assets/javascripts/app/models/post_report.js
@@ -0,0 +1,3 @@
+app.models.PostReport = Backbone.Model.extend({
+ urlRoot: '/post_report'
+});
diff --git a/app/assets/javascripts/app/views/stream_post_views.js b/app/assets/javascripts/app/views/stream_post_views.js
index 0c8faafe7..734f898ab 100644
--- a/app/assets/javascripts/app/views/stream_post_views.js
+++ b/app/assets/javascripts/app/views/stream_post_views.js
@@ -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");
diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass
index a976a28ab..27948f2f1 100644
--- a/app/assets/stylesheets/application.css.sass
+++ b/app/assets/stylesheets/application.css.sass
@@ -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
diff --git a/app/assets/stylesheets/post_report.css.scss b/app/assets/stylesheets/post_report.css.scss
new file mode 100644
index 000000000..ba865f9da
--- /dev/null
+++ b/app/assets/stylesheets/post_report.css.scss
@@ -0,0 +1,16 @@
+#post_report {
+ padding-top: 2em;
+ span {
+ display: block;
+ }
+ .content {
+ float: left;
+ }
+ .options {
+ float: right;
+ }
+ .clear {
+ clear: both;
+ padding-bottom: 1em;
+ }
+}
diff --git a/app/assets/templates/stream-element_tpl.jst.hbs b/app/assets/templates/stream-element_tpl.jst.hbs
index dae331937..21ed75fd4 100644
--- a/app/assets/templates/stream-element_tpl.jst.hbs
+++ b/app/assets/templates/stream-element_tpl.jst.hbs
@@ -10,6 +10,9 @@
{{#if loggedIn}}
{{#unless authorIsCurrentUser}}
+
+
+
diff --git a/app/controllers/post_report_controller.rb b/app/controllers/post_report_controller.rb
new file mode 100644
index 000000000..cd8dba827
--- /dev/null
+++ b/app/controllers/post_report_controller.rb
@@ -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
diff --git a/app/mailers/post_report_mailer.rb b/app/mailers/post_report_mailer.rb
new file mode 100644
index 000000000..46b6737cc
--- /dev/null
+++ b/app/mailers/post_report_mailer.rb
@@ -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
diff --git a/app/models/post_report.rb b/app/models/post_report.rb
new file mode 100644
index 000000000..a521162ad
--- /dev/null
+++ b/app/models/post_report.rb
@@ -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
diff --git a/app/models/role.rb b/app/models/role.rb
index 965044cf4..40438a035 100644
--- a/app/models/role.rb
+++ b/app/models/role.rb
@@ -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
diff --git a/app/views/admins/_admin_bar.haml b/app/views/admins/_admin_bar.haml
index 58fc53070..dff8955fa 100644
--- a/app/views/admins/_admin_bar.haml
+++ b/app/views/admins/_admin_bar.haml
@@ -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
diff --git a/app/views/post_report/index.html.haml b/app/views/post_report/index.html.haml
new file mode 100644
index 000000000..538ff39b0
--- /dev/null
+++ b/app/views/post_report/index.html.haml
@@ -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
diff --git a/app/views/post_report/post_report_email.markerb b/app/views/post_report/post_report_email.markerb
new file mode 100644
index 000000000..fb4b688f1
--- /dev/null
+++ b/app/views/post_report/post_report_email.markerb
@@ -0,0 +1,2 @@
+<%= t('notifier.post_report_email.body') %>
+
diff --git a/app/workers/mail/post_report_worker.rb b/app/workers/mail/post_report_worker.rb
new file mode 100644
index 000000000..a3f841942
--- /dev/null
+++ b/app/workers/mail/post_report_worker.rb
@@ -0,0 +1,12 @@
+module Workers
+ module Mail
+ class PostReportWorker < Base
+ sidekiq_options queue: :mail
+
+ def perform
+ PostReportMailer.new_report
+ end
+ end
+ end
+end
+
diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml
index 4204c6286..d78ae4f60 100644
--- a/config/locales/diaspora/en.yml
+++ b/config/locales/diaspora/en.yml
@@ -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: "
Post: %{title}"
+ reported_label: "
Reported by %{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."
diff --git a/config/locales/javascript/javascript.en.yml b/config/locales/javascript/javascript.en.yml
index c52b13398..170b1996b 100644
--- a/config/locales/javascript/javascript.en.yml
+++ b/config/locales/javascript/javascript.en.yml
@@ -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: ","
diff --git a/config/routes.rb b/config/routes.rb
index a3c9b6c70..4e0921a9b 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -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
diff --git a/db/migrate/20131017093025_create_post_reports.rb b/db/migrate/20131017093025_create_post_reports.rb
new file mode 100644
index 000000000..bb68ff896
--- /dev/null
+++ b/db/migrate/20131017093025_create_post_reports.rb
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index f5b374c6b..794960fe7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -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
diff --git a/spec/controllers/post_report_controller_spec.rb b/spec/controllers/post_report_controller_spec.rb
new file mode 100644
index 000000000..513e19164
--- /dev/null
+++ b/spec/controllers/post_report_controller_spec.rb
@@ -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