Merge pull request #229 from AyuntamientoMadrid/flag-as-inappropiate-171

Flag as inappropiate
This commit is contained in:
Raimond Garcia
2015-08-23 22:06:28 +02:00
40 changed files with 1026 additions and 88 deletions

View File

@@ -1,6 +1,6 @@
class Admin::OrganizationsController < Admin::BaseController
before_filter :set_valid_filters
before_filter :parse_filter
before_filter :set_valid_filters, only: :index
before_filter :parse_filter, only: :index
load_and_authorize_resource

View File

@@ -22,6 +22,16 @@ class CommentsController < ApplicationController
respond_with @comment
end
def flag_as_inappropiate
InappropiateFlag.flag!(current_user, @comment)
respond_with @comment, template: 'comments/_refresh_flag_as_inappropiate_actions'
end
def undo_flag_as_inappropiate
InappropiateFlag.unflag!(current_user, @comment)
respond_with @comment, template: 'comments/_refresh_flag_as_inappropiate_actions'
end
private
def comment_params

View File

@@ -5,13 +5,13 @@ class DebatesController < ApplicationController
respond_to :html, :js
def index
@debates = Debate.includes(:tags).search(params).page(params[:page])
@debates = Debate.includes(:tags).includes(:inappropiate_flags).search(params).page(params[:page])
set_debate_votes(@debates)
end
def show
set_debate_votes(@debate)
@comments = @debate.root_comments.with_hidden.recent.page(params[:page])
@comments = @debate.root_comments.with_hidden.includes(:inappropiate_flags).recent.page(params[:page])
end
def new
@@ -51,6 +51,16 @@ class DebatesController < ApplicationController
set_debate_votes(@debate)
end
def flag_as_inappropiate
InappropiateFlag.flag!(current_user, @debate)
respond_with @debate, template: 'debates/_refresh_flag_as_inappropiate_actions'
end
def undo_flag_as_inappropiate
InappropiateFlag.unflag!(current_user, @debate)
respond_with @debate, template: 'debates/_refresh_flag_as_inappropiate_actions'
end
private
def debate_params

View File

@@ -1,8 +1,42 @@
class Moderation::CommentsController < Moderation::BaseController
before_filter :set_valid_filters, only: :index
before_filter :parse_filter, only: :index
before_filter :load_comments, only: :index
load_and_authorize_resource
def index
@comments = @comments.send(@filter)
@comments = @comments.page(params[:page])
end
def hide
@comment = Comment.find(params[:id])
@comment.hide
end
def hide_in_moderation_screen
@comment.hide
redirect_to request.query_parameters.merge(action: :index)
end
def mark_as_reviewed
@comment.mark_as_reviewed
redirect_to request.query_parameters.merge(action: :index)
end
private
def load_comments
@comments = Comment.accessible_by(current_ability, :hide).flagged_as_inappropiate.sorted_for_moderation.includes(:commentable)
end
def set_valid_filters
@valid_filters = %w{all pending_review reviewed}
end
def parse_filter
@filter = params[:filter]
@filter = 'all' unless @valid_filters.include?(@filter)
end
end

View File

@@ -1,8 +1,42 @@
class Moderation::DebatesController < Moderation::BaseController
before_filter :set_valid_filters, only: :index
before_filter :parse_filter, only: :index
before_filter :load_debates, only: :index
load_and_authorize_resource
def index
@debates = @debates.send(@filter)
@debates = @debates.page(params[:page])
end
def hide
@debate = Debate.find(params[:id])
@debate.hide
end
def hide_in_moderation_screen
@debate.hide
redirect_to request.query_parameters.merge(action: :index)
end
def mark_as_reviewed
@debate.mark_as_reviewed
redirect_to request.query_parameters.merge(action: :index)
end
private
def load_debates
@debates = Debate.accessible_by(current_ability, :hide).flagged_as_inappropiate.sorted_for_moderation
end
def set_valid_filters
@valid_filters = %w{all pending_review reviewed}
end
def parse_filter
@filter = params[:filter]
@filter = 'all' unless @valid_filters.include?(@filter)
end
end

View File

@@ -2,9 +2,15 @@ class Ability
include CanCan::Ability
def initialize(user)
# If someone can hide something, he can also hide it
# from the moderation screen
alias_action :hide_in_moderation_screen, to: :hide
# Not logged in users
can :read, Debate
if user # logged-in users
can [:read, :update], User, id: user.id
@@ -16,6 +22,22 @@ class Ability
can :create, Comment
can :create, Debate
can :flag_as_inappropiate, Comment do |comment|
comment.author_id != user.id && !InappropiateFlag.flagged?(user, comment)
end
can :undo_flag_as_inappropiate, Comment do |comment|
comment.author_id != user.id && InappropiateFlag.flagged?(user, comment)
end
can :flag_as_inappropiate, Debate do |debate|
debate.author_id != user.id && !InappropiateFlag.flagged?(user, debate)
end
can :undo_flag_as_inappropiate, Debate do |debate|
debate.author_id != user.id && InappropiateFlag.flagged?(user, debate)
end
unless user.organization?
can :vote, Debate
can :vote, Comment
@@ -26,9 +48,22 @@ class Ability
can(:verify, Organization){ |o| !o.verified? }
can(:reject, Organization){ |o| !o.rejected? }
can :hide, Comment
can :hide, Debate
can :read, Comment
can :hide, Comment, hidden_at: nil
cannot :hide, Comment, user_id: user.id
can :mark_as_reviewed, Comment, reviewed_at: nil, hidden_at: nil
cannot :mark_as_reviewed, Comment, user_id: user.id
can :hide, Debate, hidden_at: nil
cannot :hide, Debate, author_id: user.id
can :mark_as_reviewed, Debate, reviewed_at: nil, hidden_at: nil
cannot :mark_as_reviewed, Debate, author_id: user.id
can :hide, User
cannot :hide, User, id: user.id
end
if user.administrator?

View File

@@ -11,9 +11,16 @@ class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
belongs_to :user, -> { with_hidden }
has_many :inappropiate_flags, :as => :flaggable
default_scope { includes(:user) }
scope :recent, -> { order(id: :desc) }
scope :sorted_for_moderation, -> { order(inappropiate_flags_count: :desc, updated_at: :desc) }
scope :pending_review, -> { where(reviewed_at: nil, hidden_at: nil) }
scope :reviewed, -> { where("reviewed_at IS NOT NULL AND hidden_at IS NULL") }
scope :flagged_as_inappropiate, -> { where("inappropiate_flags_count > 0") }
def self.build(commentable, user, body)
new commentable: commentable,
user_id: user.id,
@@ -28,10 +35,18 @@ class Comment < ActiveRecord::Base
commentable if commentable.class == Debate
end
def author_id
user_id
end
def author
user
end
def author=(author)
self.user= author
end
def total_votes
votes_for.size
end
@@ -40,6 +55,14 @@ class Comment < ActiveRecord::Base
hidden? || user.hidden?
end
def reviewed?
reviewed_at.present?
end
def mark_as_reviewed
update(reviewed_at: Time.now)
end
# TODO: faking counter cache since there is a bug with acts_as_nested_set :counter_cache
# Remove when https://github.com/collectiveidea/awesome_nested_set/issues/294 is fixed
# and reset counters using

View File

@@ -12,6 +12,7 @@ class Debate < ActiveRecord::Base
acts_as_paranoid column: :hidden_at
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
has_many :inappropiate_flags, :as => :flaggable
validates :title, presence: true
validates :description, presence: true
@@ -22,6 +23,11 @@ class Debate < ActiveRecord::Base
before_validation :sanitize_description
before_validation :sanitize_tag_list
scope :sorted_for_moderation, -> { order(inappropiate_flags_count: :desc, updated_at: :desc) }
scope :pending_review, -> { where(reviewed_at: nil, hidden_at: nil) }
scope :reviewed, -> { where("reviewed_at IS NOT NULL AND hidden_at IS NULL") }
scope :flagged_as_inappropiate, -> { where("inappropiate_flags_count > 0") }
# Ahoy setup
visitable # Ahoy will automatically assign visit_id on create
@@ -68,6 +74,14 @@ class Debate < ActiveRecord::Base
count < 0 ? 0 : count
end
def reviewed?
reviewed_at.present?
end
def mark_as_reviewed
update(reviewed_at: Time.now)
end
protected
def sanitize_description

View File

@@ -0,0 +1,41 @@
class InappropiateFlag < ActiveRecord::Base
belongs_to :user
belongs_to :flaggable, polymorphic: true, counter_cache: true, touch: :flagged_as_inappropiate_at
scope(:by_user_and_flaggable, lambda do |user, flaggable|
where(user_id: user.id,
flaggable_type: flaggable.class.to_s,
flaggable_id: flaggable.id)
end)
class AlreadyFlaggedError < StandardError
def initialize
super "The flaggable was already flagged as inappropiate by this user"
end
end
class NotFlaggedError < StandardError
def initialize
super "The flaggable was not flagged as inappropiate by this user"
end
end
def self.flag!(user, flaggable)
raise AlreadyFlaggedError if flagged?(user, flaggable)
create(user: user, flaggable: flaggable)
end
def self.unflag!(user, flaggable)
flags = by_user_and_flaggable(user, flaggable)
raise NotFlaggedError if flags.empty?
flags.destroy_all
end
def self.flagged?(user, flaggable)
!! by_user_and_flaggable(user, flaggable).try(:first)
end
end

View File

@@ -10,6 +10,7 @@ class User < ActiveRecord::Base
has_one :administrator
has_one :moderator
has_one :organization
has_many :inappropiate_flags
validates :username, presence: true, unless: :organization?
validates :official_level, inclusion: {in: 0..5}

View File

@@ -41,6 +41,11 @@
</span>
<% end %>
&nbsp;&bullet;&nbsp;<%= time_ago_in_words(comment.created_at) %>
<span class="right js-flag-as-inappropiate-actions">
<%= render 'comments/flag_as_inappropiate_actions', comment: comment %>
</span>
</div>
<% if comment.user.official? && comment.user_id == @debate.author_id %>

View File

@@ -0,0 +1,6 @@
<% if can? :flag_as_inappropiate, comment %>
<%= link_to t('shared.flag_as_inappropiate'), flag_as_inappropiate_comment_path(comment), method: :put, remote: true %>
<% end %>
<% if can? :undo_flag_as_inappropiate, comment %>
<%= link_to t('shared.undo_flag_as_inappropiate'), undo_flag_as_inappropiate_comment_path(comment), method: :put, remote: true %>
<% end %>

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@comment) %> .js-flag-as-inappropiate-actions").html('<%= j render("comments/flag_as_inappropiate_actions", comment: @comment) %>');

View File

@@ -0,0 +1,6 @@
<% if can? :flag_as_inappropiate, debate %>
<%= link_to t('shared.flag_as_inappropiate'), flag_as_inappropiate_debate_path(debate), method: :put, remote: true %>
<% end %>
<% if can? :undo_flag_as_inappropiate, debate %>
<%= link_to t('shared.undo_flag_as_inappropiate'), undo_flag_as_inappropiate_debate_path(debate), method: :put, remote: true %>
<% end %>

View File

@@ -0,0 +1 @@
$("#<%= dom_id(@debate) %> .js-flag-as-inappropiate-actions").html('<%= j render("debates/flag_as_inappropiate_actions", debate: @debate) %>');

View File

@@ -41,6 +41,10 @@
<span class="bullet">&nbsp;&bullet;&nbsp;</span>
<i class="icon-comments"></i>&nbsp;
<%= link_to t("debates.show.comments", count: @debate.comment_threads.count), "#comments" %>
<span class='right js-flag-as-inappropiate-actions'>
<%= render 'debates/flag_as_inappropiate_actions', debate: @debate %>
</span>
</div>
<%= @debate.description %>

View File

@@ -1,3 +1,21 @@
<div id="moderation_menu">
Moderation links
</div>
<nav class="admin-sidebar">
<ul id="moderation_menu">
<li>
<%= t("moderation.dashboard.index.title") %>
</li>
<li <%= 'class=active' if controller_name == 'debates' %>>
<%= link_to moderation_debates_path do %>
<i class="icon-eye"></i>
<%= t('moderation.menu.flagged_debates') %>
<% end %>
</li>
<li <%= 'class=active' if controller_name == 'comments' %>>
<%= link_to moderation_comments_path do %>
<i class="icon-comment-quotes"></i>
<%= t('moderation.menu.flagged_comments') %>
<% end %>
</li>
</ul>
</nav>

View File

@@ -0,0 +1,50 @@
<h2><%= t('moderation.comments.index.title') %></h2>
<p>
<%= t('moderation.comments.index.filter') %>:
<% @valid_filters.each do |filter| %>
<% if @filter == filter %>
<%= t("moderation.comments.index.filters.#{filter}") %>
<% else %>
<%= link_to t("moderation.comments.index.filters.#{filter}"),
moderation_comments_path(filter: filter) %>
<% end %>
<% end %>
</p>
<h3><%= page_entries_info @comments %></h3>
<table>
<tr>
<th><%= t('moderation.comments.index.headers.flags') %></th>
<th><%= t('moderation.comments.index.headers.updated_at') %></th>
<th><%= t('moderation.comments.index.headers.commentable_type') %></th>
<th><%= t('moderation.comments.index.headers.commentable') %></th>
<th><%= t('moderation.comments.index.headers.comment') %></th>
</tr>
<% @comments.each do |comment| %>
<tr id="comment_<%= comment.id %>">
<td><%= comment.inappropiate_flags_count %></td>
<td><%= l comment.updated_at.to_date %></td>
<td><%= comment.commentable_type.constantize.model_name.human %></td>
<td><%= link_to comment.commentable.title, comment.commentable %></td>
<td><%= comment.body %></td>
<td>
<%= link_to t('moderation.comments.index.hide'), hide_in_moderation_screen_moderation_comment_path(comment, request.query_parameters), method: :put %>
</td>
<% if can? :mark_as_reviewed, comment %>
<td>
<%= link_to t('moderation.comments.index.mark_as_reviewed'), mark_as_reviewed_moderation_comment_path(comment, request.query_parameters), method: :put %>
</td>
<% end %>
<% if comment.reviewed? %>
<td>
<%= t('moderation.comments.index.reviewed') %>
</td>
<% end %>
</tr>
<% end %>
</table>
<%= paginate @comments %>

View File

@@ -0,0 +1,48 @@
<h2><%= t('moderation.debates.index.title') %></h2>
<p>
<%= t('moderation.debates.index.filter') %>:
<% @valid_filters.each do |filter| %>
<% if @filter == filter %>
<%= t("moderation.debates.index.filters.#{filter}") %>
<% else %>
<%= link_to t("moderation.debates.index.filters.#{filter}"),
moderation_debates_path(filter: filter) %>
<% end %>
<% end %>
</p>
<h3><%= page_entries_info @debates %></h3>
<table>
<tr>
<th><%= t('moderation.debates.index.headers.flags') %></th>
<th><%= t('moderation.debates.index.headers.updated_at') %></th>
<th><%= t('moderation.debates.index.headers.title') %></th>
<th><%= t('moderation.debates.index.headers.description') %></th>
</tr>
<% @debates.each do |debate| %>
<tr id="debate_<%= debate.id %>">
<td><%= debate.inappropiate_flags_count %></td>
<td><%= l debate.updated_at.to_date %></td>
<td><%= link_to debate.title, debate %></td>
<td><%= debate.description %></td>
<td>
<%= link_to t('moderation.debates.index.hide'), hide_in_moderation_screen_moderation_debate_path(debate, request.query_parameters), method: :put %>
</td>
<% if can? :mark_as_reviewed, debate %>
<td>
<%= link_to t('moderation.debates.index.mark_as_reviewed'), mark_as_reviewed_moderation_debate_path(debate, request.query_parameters), method: :put %>
</td>
<% end %>
<% if debate.reviewed? %>
<td>
<%= t('moderation.debates.index.reviewed') %>
</td>
<% end %>
</tr>
<% end %>
</table>
<%= paginate @debates %>

View File

@@ -6,7 +6,7 @@
</li>
<% end %>
<% if current_user.moderator? %>
<% if current_user.moderator? || current_user.administrator? %>
<li>
<%= link_to t("layouts.header.moderation"), moderation_root_path %>
</li>

View File

@@ -130,6 +130,8 @@ en:
shared:
tags_cloud:
tags: Topics
flag_as_inappropiate: Flag as inappropiate
undo_flag_as_inappropiate: Undo flag as inappropiate
collective: Collective
mailer:
comment:

View File

@@ -130,6 +130,8 @@ es:
shared:
tags_cloud:
tags: Temas
flag_as_inappropiate: Denunciar como inapropiado
undo_flag_as_inappropiate: Deshacer denunciar como inapropiado
collective: Colectivo
mailer:
comment:

View File

@@ -1,6 +1,42 @@
en:
moderation:
menu:
flagged_debates: Debates
flagged_comments: Comments
dashboard:
index:
title: Moderation
comments:
index:
title: Comments flagged as inappropiate
headers:
flags: Flags
updated_at: Date
commentable_type: Type
commentable: Root
comment: Comment
hide: Hide
mark_as_reviewed: Mark as reviewed
reviewed: Reviewed
filter: Filter
filters:
all: All
pending_review: Pending
reviewed: Reviewed
debates:
index:
title: Debates flagged as inappropiate
headers:
flags: Flags
updated_at: Date
title: Title
description: Description
hide: Hide
mark_as_reviewed: Mark as reviewed
reviewed: Reviewed
filter: Filter
filters:
all: All
pending_review: Pending
reviewed: Reviewed

View File

@@ -1,7 +1,43 @@
es:
moderation:
menu:
flagged_debates: Debates
flagged_comments: Comentarios
dashboard:
index:
title: Moderación
comments:
index:
title: Comentarios Denunciados como Inapropiados
headers:
flags: Denuncias
updated_at: Fecha
commentable_type: Tipo
commentable: Raíz
comment: Comentario
hide: Ocultar
mark_as_reviewed: Marcar como revisado
reviewed: Revisado
filter: Filtrar
filters:
all: Todos
pending_review: Pendientes
reviewed: Revisados
debates:
index:
title: Debates Denunciados como Inapropiados
headers:
flags: Denuncias
updated_at: Fecha
title: Título
description: Descripción
hide: Ocultar
mark_as_reviewed: Marcar como revisado
reviewed: Revisado
filter: Filtrar
filters:
all: Todos
pending_review: Pendientes
reviewed: Revisados

View File

@@ -13,10 +13,18 @@ Rails.application.routes.draw do
root 'welcome#index'
resources :debates do
member { post :vote }
member do
post :vote
put :flag_as_inappropiate
put :undo_flag_as_inappropiate
end
resources :comments, only: :create, shallow: true do
member { post :vote }
member do
post :vote
put :flag_as_inappropiate
put :undo_flag_as_inappropiate
end
end
end
@@ -63,12 +71,20 @@ Rails.application.routes.draw do
member { put :hide }
end
resources :debates, only: [] do
member { put :hide }
resources :debates, only: :index do
member do
put :hide
put :hide_in_moderation_screen
put :mark_as_reviewed
end
end
resources :comments, only: [] do
member { put :hide }
resources :comments, only: :index do
member do
put :hide
put :hide_in_moderation_screen
put :mark_as_reviewed
end
end
end

View File

@@ -0,0 +1,15 @@
class CreateInappropiateFlags < ActiveRecord::Migration
def change
create_table :inappropiate_flags do |t|
t.belongs_to :user, index: true, foreign_key: true
t.string :flaggable_type
t.integer :flaggable_id
t.timestamps
end
add_index :inappropiate_flags, [:flaggable_type, :flaggable_id]
add_index :inappropiate_flags, [:user_id, :flaggable_type, :flaggable_id], :name => "access_inappropiate_flags"
end
end

View File

@@ -0,0 +1,6 @@
class AddInappropiateFlagFieldsToComments < ActiveRecord::Migration
def change
add_column :comments, :flagged_as_inappropiate_at, :datetime
add_column :comments, :inappropiate_flags_count, :integer, default: 0
end
end

View File

@@ -0,0 +1,6 @@
class AddInappropiateFlagFieldsToDebates < ActiveRecord::Migration
def change
add_column :debates, :flagged_as_inappropiate_at, :datetime
add_column :debates, :inappropiate_flags_count, :integer, default: 0
end
end

View File

@@ -0,0 +1,5 @@
class AddReviewedAtToComments < ActiveRecord::Migration
def change
add_column :comments, :reviewed_at, :datetime
end
end

View File

@@ -0,0 +1,5 @@
class AddReviewedAtToDebates < ActiveRecord::Migration
def change
add_column :debates, :reviewed_at, :datetime
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150821130019) do
ActiveRecord::Schema.define(version: 20150821180155) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -49,6 +49,9 @@ ActiveRecord::Schema.define(version: 20150821130019) do
t.datetime "updated_at"
t.integer "children_count", default: 0
t.datetime "hidden_at"
t.datetime "flagged_as_inappropiate_at"
t.integer "inappropiate_flags_count", default: 0
t.datetime "reviewed_at"
end
add_index "comments", ["commentable_id", "commentable_type"], name: "index_comments_on_commentable_id_and_commentable_type", using: :btree
@@ -63,10 +66,25 @@ ActiveRecord::Schema.define(version: 20150821130019) do
t.datetime "updated_at", null: false
t.datetime "hidden_at"
t.string "visit_id"
t.datetime "flagged_as_inappropiate_at"
t.integer "inappropiate_flags_count", default: 0
t.datetime "reviewed_at"
end
add_index "debates", ["hidden_at"], name: "index_debates_on_hidden_at", using: :btree
create_table "inappropiate_flags", force: :cascade do |t|
t.integer "user_id"
t.string "flaggable_type"
t.integer "flaggable_id"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "inappropiate_flags", ["flaggable_type", "flaggable_id"], name: "index_inappropiate_flags_on_flaggable_type_and_flaggable_id", using: :btree
add_index "inappropiate_flags", ["user_id", "flaggable_type", "flaggable_id"], name: "access_inappropiate_flags", using: :btree
add_index "inappropiate_flags", ["user_id"], name: "index_inappropiate_flags_on_user_id", using: :btree
create_table "moderators", force: :cascade do |t|
t.integer "user_id"
end
@@ -194,6 +212,7 @@ ActiveRecord::Schema.define(version: 20150821130019) do
add_index "votes", ["voter_id", "voter_type", "vote_scope"], name: "index_votes_on_voter_id_and_voter_type_and_vote_scope", using: :btree
add_foreign_key "administrators", "users"
add_foreign_key "inappropiate_flags", "users"
add_foreign_key "moderators", "users"
add_foreign_key "organizations", "users"
end

View File

@@ -16,6 +16,16 @@ FactoryGirl.define do
trait :hidden do
hidden_at Time.now
end
trait :reviewed do
reviewed_at Time.now
end
trait :flagged_as_inappropiate do
after :create do |debate|
InappropiateFlag.flag!(FactoryGirl.create(:user), debate)
end
end
end
factory :vote do
@@ -32,6 +42,16 @@ FactoryGirl.define do
trait :hidden do
hidden_at Time.now
end
trait :reviewed do
reviewed_at Time.now
end
trait :flagged_as_inappropiate do
after :create do |debate|
InappropiateFlag.flag!(FactoryGirl.create(:user), debate)
end
end
end
factory :administrator do
@@ -45,14 +65,14 @@ FactoryGirl.define do
factory :organization do
user
sequence(:name) { |n| "org#{n}" }
trait :verified do
verified_at Time.now
end
factory :verified_organization, parent: :organization do
verified_at { Time.now}
trait :rejected do
rejected_at Time.now
end
factory :rejected_organization, parent: :organization do
rejected_at { Time.now}
end
factory :tag, class: 'ActsAsTaggableOn::Tag' do
@@ -82,4 +102,5 @@ FactoryGirl.define do
id { SecureRandom.uuid }
started_at DateTime.now
end
end

View File

@@ -25,7 +25,7 @@ feature 'Admin::Organizations' do
end
scenario "verified organizations have link to reject" do
organization = create(:verified_organization)
organization = create(:organization, :verified)
visit admin_organizations_path
expect(page).to have_content ('Verified')
@@ -40,7 +40,7 @@ feature 'Admin::Organizations' do
end
scenario "rejected organizations have link to verify" do
organization = create(:rejected_organization)
organization = create(:organization, :rejected)
visit admin_organizations_path
expect(page).to have_link('Verify')
@@ -87,8 +87,8 @@ feature 'Admin::Organizations' do
scenario "Filtering organizations" do
create(:organization, name: "Pending Organization")
create(:rejected_organization, name: "Rejected Organization")
create(:verified_organization, name: "Verified Organization")
create(:organization, :rejected, name: "Rejected Organization")
create(:organization, :verified, name: "Verified Organization")
visit admin_organizations_path(filter: 'all')
expect(page).to have_content('Pending Organization')
@@ -112,7 +112,8 @@ feature 'Admin::Organizations' do
end
scenario "Verifying organization links remember the pagination setting and the filter" do
30.times { create(:organization) }
per_page = Kaminari.config.default_per_page
(per_page + 2).times { create(:organization) }
visit admin_organizations_path(filter: 'pending', page: 2)

View File

@@ -133,4 +133,39 @@ feature 'Comments' do
expect(page).to have_css(".comment.comment.comment.comment.comment.comment.comment.comment")
end
scenario "Flagging as inappropiate", :js do
user = create(:user)
debate = create(:debate)
comment = create(:comment, commentable: debate)
login_as(user)
visit debate_path(debate)
within "#comment_#{comment.id}" do
expect(page).to_not have_link "Undo flag as inappropiate"
click_on 'Flag as inappropiate'
expect(page).to have_link "Undo flag as inappropiate"
end
expect(InappropiateFlag.flagged?(user, comment)).to be
end
scenario "Undoing flagging as inappropiate", :js do
user = create(:user)
debate = create(:debate)
comment = create(:comment, commentable: debate)
InappropiateFlag.flag!(user, comment)
login_as(user)
visit debate_path(debate)
within "#comment_#{comment.id}" do
expect(page).to_not have_link("Flag as inappropiate", exact: true)
click_on 'Undo flag as inappropiate'
expect(page).to have_link("Flag as inappropiate", exact: true)
end
expect(InappropiateFlag.flagged?(user, comment)).to_not be
end
end

View File

@@ -315,4 +315,37 @@ feature 'Debates' do
end
end
scenario "Flagging as inappropiate", :js do
user = create(:user)
debate = create(:debate)
login_as(user)
visit debate_path(debate)
within "#debate_#{debate.id}" do
expect(page).to_not have_link "Undo flag as inappropiate"
click_on 'Flag as inappropiate'
expect(page).to have_link "Undo flag as inappropiate"
end
expect(InappropiateFlag.flagged?(user, debate)).to be
end
scenario "Undoing flagging as inappropiate", :js do
user = create(:user)
debate = create(:debate)
InappropiateFlag.flag!(user, debate)
login_as(user)
visit debate_path(debate)
within "#debate_#{debate.id}" do
expect(page).to_not have_link("Flag as inappropiate", exact: true)
click_on 'Undo flag as inappropiate'
expect(page).to have_link("Flag as inappropiate", exact: true)
end
expect(InappropiateFlag.flagged?(user, debate)).to_not be
end
end

View File

@@ -2,6 +2,8 @@ require 'rails_helper'
feature 'Moderate Comments' do
feature 'Hiding Comments' do
scenario 'Hide', :js do
citizen = create(:user)
moderator = create(:moderator)
@@ -31,7 +33,7 @@ feature 'Moderate Comments' do
debate = create(:debate)
comment = create(:comment, commentable: debate, body: 'SPAM')
reply = create(:comment, commentable: debate, body: 'Acceptable reply', parent_id: comment.id)
create(:comment, commentable: debate, body: 'Acceptable reply', parent_id: comment.id)
login_as(moderator.user)
visit debate_path(debate)
@@ -50,13 +52,14 @@ feature 'Moderate Comments' do
expect(page).to have_content('Acceptable reply')
end
end
scenario 'Moderator actions' do
scenario 'Moderator actions in the comment' do
citizen = create(:user)
moderator = create(:moderator)
debate = create(:debate)
comment = create(:comment, commentable: debate)
create(:comment, commentable: debate)
login_as(moderator.user)
visit debate_path(debate)
@@ -69,4 +72,113 @@ feature 'Moderate Comments' do
expect(page).to_not have_css("#moderator-comment-actions")
end
feature '/moderation/ menu' do
background do
moderator = create(:moderator)
login_as(moderator.user)
end
scenario "Current filter is properly highlighted" do
visit moderation_comments_path
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_comments_path(filter: 'all')
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_comments_path(filter: 'pending_review')
expect(page).to have_link('All')
expect(page).to_not have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_comments_path(filter: 'reviewed')
expect(page).to have_link('All')
expect(page).to have_link('Pending')
expect(page).to_not have_link('Reviewed')
end
scenario "Filtering comments" do
create(:comment, :flagged_as_inappropiate, body: "Pending comment")
create(:comment, :flagged_as_inappropiate, :hidden, body: "Hidden comment")
create(:comment, :flagged_as_inappropiate, :reviewed, body: "Reviewed comment")
visit moderation_comments_path(filter: 'all')
expect(page).to have_content('Pending comment')
expect(page).to_not have_content('Hidden comment')
expect(page).to have_content('Reviewed comment')
visit moderation_comments_path(filter: 'pending_review')
expect(page).to have_content('Pending comment')
expect(page).to_not have_content('Hidden comment')
expect(page).to_not have_content('Reviewed comment')
visit moderation_comments_path(filter: 'reviewed')
expect(page).to_not have_content('Pending comment')
expect(page).to_not have_content('Hidden comment')
expect(page).to have_content('Reviewed comment')
end
scenario "Reviewing links remember the pagination setting and the filter" do
per_page = Kaminari.config.default_per_page
(per_page + 2).times { create(:comment, :flagged_as_inappropiate) }
visit moderation_comments_path(filter: 'pending_review', page: 2)
click_link('Mark as reviewed', match: :first)
uri = URI.parse(current_url)
query_params = Rack::Utils.parse_nested_query(uri.query).symbolize_keys
expect(query_params[:filter]).to eq('pending_review')
expect(query_params[:page]).to eq('2')
end
feature 'A flagged comment exists' do
background do
debate = create(:debate, title: 'Democracy')
@comment = create(:comment, :flagged_as_inappropiate, commentable: debate, body: 'spammy spam')
visit moderation_comments_path
end
scenario 'It is displayed with the correct attributes' do
within("#comment_#{@comment.id}") do
expect(page).to have_link('Democracy')
expect(page).to have_content('spammy spam')
expect(page).to have_content('1')
expect(page).to have_link('Hide')
expect(page).to have_link('Mark as reviewed')
end
end
scenario 'Hiding the comment' do
within("#comment_#{@comment.id}") do
click_link('Hide')
end
expect(current_path).to eq(moderation_comments_path)
expect(page).to_not have_selector("#comment_#{@comment.id}")
expect(@comment.reload).to be_hidden
end
scenario 'Marking the comment as reviewed' do
within("#comment_#{@comment.id}") do
click_link('Mark as reviewed')
end
expect(current_path).to eq(moderation_comments_path)
within("#comment_#{@comment.id}") do
expect(page).to have_content('Reviewed')
end
expect(@comment.reload).to be_reviewed
end
end
end
end

View File

@@ -23,4 +23,113 @@ feature 'Moderate debates' do
expect(page).to have_css('.debate', count: 0)
end
feature '/moderation/ menu' do
background do
moderator = create(:moderator)
login_as(moderator.user)
end
scenario "Current filter is properly highlighted" do
visit moderation_debates_path
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_debates_path(filter: 'all')
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_debates_path(filter: 'pending_review')
expect(page).to have_link('All')
expect(page).to_not have_link('Pending')
expect(page).to have_link('Reviewed')
visit moderation_debates_path(filter: 'reviewed')
expect(page).to have_link('All')
expect(page).to have_link('Pending')
expect(page).to_not have_link('Reviewed')
end
scenario "Filtering debates" do
create(:debate, :flagged_as_inappropiate, title: "Pending debate")
create(:debate, :flagged_as_inappropiate, :hidden, title: "Hidden debate")
create(:debate, :flagged_as_inappropiate, :reviewed, title: "Reviewed debate")
visit moderation_debates_path(filter: 'all')
expect(page).to have_content('Pending debate')
expect(page).to_not have_content('Hidden debate')
expect(page).to have_content('Reviewed debate')
visit moderation_debates_path(filter: 'pending_review')
expect(page).to have_content('Pending debate')
expect(page).to_not have_content('Hidden debate')
expect(page).to_not have_content('Reviewed debate')
visit moderation_debates_path(filter: 'reviewed')
expect(page).to_not have_content('Pending debate')
expect(page).to_not have_content('Hidden debate')
expect(page).to have_content('Reviewed debate')
end
scenario "Reviewing links remember the pagination setting and the filter" do
per_page = Kaminari.config.default_per_page
(per_page + 2).times { create(:debate, :flagged_as_inappropiate) }
visit moderation_debates_path(filter: 'pending_review', page: 2)
click_link('Mark as reviewed', match: :first)
uri = URI.parse(current_url)
query_params = Rack::Utils.parse_nested_query(uri.query).symbolize_keys
expect(query_params[:filter]).to eq('pending_review')
expect(query_params[:page]).to eq('2')
end
feature 'A flagged debate exists' do
background do
@debate = create(:debate, :flagged_as_inappropiate, title: 'spammy spam', description: 'buy buy buy')
visit moderation_debates_path
end
scenario 'It is displayed with the correct attributes' do
within("#debate_#{@debate.id}") do
expect(page).to have_link('spammy spam')
expect(page).to have_content('buy buy buy')
expect(page).to have_content('1')
expect(page).to have_link('Hide')
expect(page).to have_link('Mark as reviewed')
end
end
scenario 'Hiding the debate' do
within("#debate_#{@debate.id}") do
click_link('Hide')
end
expect(current_path).to eq(moderation_debates_path)
expect(page).to_not have_selector("#debate_#{@debate.id}")
expect(@debate.reload).to be_hidden
end
scenario 'Marking the debate as reviewed' do
within("#debate_#{@debate.id}") do
click_link('Mark as reviewed')
end
expect(current_path).to eq(moderation_debates_path)
within("#debate_#{@debate.id}") do
expect(page).to have_content('Reviewed')
end
expect(@debate.reload).to be_reviewed
end
end
end
end

View File

@@ -5,6 +5,9 @@ feature 'Admin' do
scenario 'Access as regular user is not authorized' do
login_as(user)
visit root_path
expect(page).to_not have_link("Moderation")
visit moderation_root_path
expect(current_path).to eq(root_path)
@@ -15,7 +18,10 @@ feature 'Admin' do
create(:moderator, user: user)
login_as(user)
visit moderation_root_path
visit root_path
expect(page).to have_link("Moderation")
click_on "Moderation"
expect(current_path).to eq(moderation_root_path)
expect(page).to_not have_content "not authorized"
@@ -25,7 +31,10 @@ feature 'Admin' do
create(:administrator, user: user)
login_as(user)
visit moderation_root_path
visit root_path
expect(page).to have_link("Moderation")
click_on "Moderation"
expect(current_path).to eq(moderation_root_path)
expect(page).to_not have_content "not authorized"

View File

@@ -22,12 +22,48 @@ describe Ability do
it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) }
it { should be_able_to(:show, user) }
it { should be_able_to(:edit, user) }
it { should be_able_to(:create, Comment) }
it { should be_able_to(:vote, Comment) }
describe 'flagging content as inappropiate' do
it { should be_able_to(:flag_as_inappropiate, debate) }
it { should_not be_able_to(:undo_flag_as_inappropiate, debate) }
it { should be_able_to(:flag_as_inappropiate, comment) }
it { should_not be_able_to(:undo_flag_as_inappropiate, comment) }
describe "own comments" do
let(:own_comment) { create(:comment, author: user) }
it { should_not be_able_to(:flag_as_inappropiate, own_comment) }
it { should_not be_able_to(:undo_flag_as_inappropiate, own_comment) }
end
describe "own debates" do
let(:own_debate) { create(:debate, author: user) }
it { should_not be_able_to(:flag_as_inappropiate, own_debate) }
it { should_not be_able_to(:undo_flag_as_inappropiate, own_debate) }
end
describe "already-flagged comments" do
before(:each) { InappropiateFlag.flag!(user, comment) }
it { should_not be_able_to(:flag_as_inappropiate, comment) }
it { should be_able_to(:undo_flag_as_inappropiate, comment) }
end
describe "already-flagged debates" do
before(:each) { InappropiateFlag.flag!(user, debate) }
it { should_not be_able_to(:flag_as_inappropiate, debate) }
it { should be_able_to(:undo_flag_as_inappropiate, debate) }
end
end
describe "other users" do
let(:other_user) { create(:user) }
it { should_not be_able_to(:show, other_user) }
@@ -66,6 +102,7 @@ describe Ability do
before { create(:moderator, user: user) }
let(:other_user) { create(:user) }
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) }
@@ -74,8 +111,8 @@ describe Ability do
describe "organizations" do
let(:pending_organization) { create(:organization) }
let(:rejected_organization) { create(:rejected_organization) }
let(:verified_organization) { create(:verified_organization) }
let(:rejected_organization) { create(:organization, :rejected) }
let(:verified_organization) { create(:organization, :verified) }
it { should be_able_to( :verify, pending_organization) }
it { should be_able_to( :reject, pending_organization) }
@@ -87,14 +124,42 @@ describe Ability do
it { should be_able_to( :verify, rejected_organization) }
end
describe "hiding, reviewing and restoring" do
let(:own_comment) { create(:comment, author: user) }
let(:own_debate) { create(:debate, author: user) }
let(:hidden_comment) { create(:comment, :hidden) }
let(:hidden_debate) { create(:debate, :hidden) }
let(:reviewed_comment) { create(:comment, :reviewed) }
let(:reviewed_debate) { create(:debate, :reviewed) }
it { should be_able_to(:hide, comment) }
it { should be_able_to(:hide_in_moderation_screen, comment) }
it { should_not be_able_to(:hide, hidden_comment) }
it { should_not be_able_to(:hide, own_comment) }
it { should be_able_to(:hide, debate) }
it { should be_able_to(:hide_in_moderation_screen, debate) }
it { should_not be_able_to(:hide, hidden_debate) }
it { should_not be_able_to(:hide, own_debate) }
it { should be_able_to(:mark_as_reviewed, comment) }
it { should_not be_able_to(:mark_as_reviewed, hidden_comment) }
it { should_not be_able_to(:mark_as_reviewed, reviewed_comment) }
it { should_not be_able_to(:mark_as_reviewed, own_comment) }
it { should be_able_to(:mark_as_reviewed, debate) }
it { should_not be_able_to(:mark_as_reviewed, hidden_debate) }
it { should_not be_able_to(:mark_as_reviewed, reviewed_debate) }
it { should_not be_able_to(:mark_as_reviewed, own_debate) }
it { should_not be_able_to(:hide, user) }
it { should be_able_to(:hide, other_user) }
it { should_not be_able_to(:restore, comment) }
it { should_not be_able_to(:restore, debate) }
it { should_not be_able_to(:restore, other_user) }
end
end
describe "Administrator" do
let(:user) { create(:user) }

View File

@@ -0,0 +1,64 @@
require 'rails_helper'
describe InappropiateFlag do
let(:user) { create(:user) }
let(:comment) { create(:comment) }
describe '.flag!' do
it 'creates a flag when there is none' do
expect { described_class.flag!(user, comment) }.to change{ InappropiateFlag.count }.by(1)
expect(InappropiateFlag.last.user).to eq(user)
expect(InappropiateFlag.last.flaggable).to eq(comment)
end
it 'raises an error if the flag has already been created' do
described_class.flag!(user, comment)
expect { described_class.flag!(user, comment) }.to raise_error(InappropiateFlag::AlreadyFlaggedError)
end
it 'increases the flag count' do
expect { described_class.flag!(user, comment) }.to change{ comment.reload.inappropiate_flags_count }.by(1)
end
it 'updates the flagged_as date' do
expect { described_class.flag!(user, comment) }.to change{ comment.reload.flagged_as_inappropiate_at }
end
end
describe '.unflag!' do
it 'raises an error if the flag does not exist' do
expect { described_class.unflag!(user, comment) }.to raise_error(InappropiateFlag::NotFlaggedError)
end
describe 'when the flag already exists' do
before(:each) { described_class.flag!(user, comment) }
it 'removes an existing flag' do
expect { described_class.unflag!(user, comment) }.to change{ InappropiateFlag.count }.by(-1)
end
it 'decreases the flag count' do
expect { described_class.unflag!(user, comment) }.to change{ comment.reload.inappropiate_flags_count }.by(-1)
end
it 'does not update the flagged_as date' do
expect { described_class.unflag!(user, comment) }.to_not change{ comment.flagged_as_inappropiate_at }
end
end
end
describe '.flagged?' do
it 'returns false when the user has not flagged the comment' do
expect(described_class.flagged?(user, comment)).to_not be
end
it 'returns true when the user has flagged the comment' do
described_class.flag!(user, comment)
expect(described_class.flagged?(user, comment)).to be
end
end
end