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