Merge pull request #229 from AyuntamientoMadrid/flag-as-inappropiate-171
Flag as inappropiate
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
41
app/models/inappropiate_flag.rb
Normal file
41
app/models/inappropiate_flag.rb
Normal 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
|
||||
@@ -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}
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
</span>
|
||||
<% end %>
|
||||
• <%= 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 %>
|
||||
|
||||
@@ -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 %>
|
||||
@@ -0,0 +1 @@
|
||||
$("#<%= dom_id(@comment) %> .js-flag-as-inappropiate-actions").html('<%= j render("comments/flag_as_inappropiate_actions", comment: @comment) %>');
|
||||
6
app/views/debates/_flag_as_inappropiate_actions.html.erb
Normal file
6
app/views/debates/_flag_as_inappropiate_actions.html.erb
Normal 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 %>
|
||||
@@ -0,0 +1 @@
|
||||
$("#<%= dom_id(@debate) %> .js-flag-as-inappropiate-actions").html('<%= j render("debates/flag_as_inappropiate_actions", debate: @debate) %>');
|
||||
@@ -41,6 +41,10 @@
|
||||
<span class="bullet"> • </span>
|
||||
<i class="icon-comments"></i>
|
||||
<%= 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 %>
|
||||
|
||||
@@ -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>
|
||||
|
||||
50
app/views/moderation/comments/index.html.erb
Normal file
50
app/views/moderation/comments/index.html.erb
Normal 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 %>
|
||||
|
||||
48
app/views/moderation/debates/index.html.erb
Normal file
48
app/views/moderation/debates/index.html.erb
Normal 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 %>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
15
db/migrate/20150820103351_create_inappropiate_flags.rb
Normal file
15
db/migrate/20150820103351_create_inappropiate_flags.rb
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
5
db/migrate/20150821180131_add_reviewed_at_to_comments.rb
Normal file
5
db/migrate/20150821180131_add_reviewed_at_to_comments.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddReviewedAtToComments < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :comments, :reviewed_at, :datetime
|
||||
end
|
||||
end
|
||||
5
db/migrate/20150821180155_add_reviewed_at_to_debates.rb
Normal file
5
db/migrate/20150821180155_add_reviewed_at_to_debates.rb
Normal file
@@ -0,0 +1,5 @@
|
||||
class AddReviewedAtToDebates < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :debates, :reviewed_at, :datetime
|
||||
end
|
||||
end
|
||||
31
db/schema.rb
31
db/schema.rb
@@ -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"
|
||||
@@ -41,14 +41,17 @@ ActiveRecord::Schema.define(version: 20150821130019) do
|
||||
t.string "title"
|
||||
t.text "body"
|
||||
t.string "subject"
|
||||
t.integer "user_id", null: false
|
||||
t.integer "user_id", null: false
|
||||
t.integer "parent_id"
|
||||
t.integer "lft"
|
||||
t.integer "rgt"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "children_count", default: 0
|
||||
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
|
||||
@@ -56,17 +59,32 @@ ActiveRecord::Schema.define(version: 20150821130019) do
|
||||
add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree
|
||||
|
||||
create_table "debates", force: :cascade do |t|
|
||||
t.string "title", limit: 80
|
||||
t.string "title", limit: 80
|
||||
t.text "description"
|
||||
t.integer "author_id"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
t.datetime "created_at", null: false
|
||||
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
|
||||
|
||||
@@ -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}" }
|
||||
end
|
||||
|
||||
factory :verified_organization, parent: :organization do
|
||||
verified_at { Time.now}
|
||||
end
|
||||
trait :verified do
|
||||
verified_at Time.now
|
||||
end
|
||||
|
||||
factory :rejected_organization, parent: :organization do
|
||||
rejected_at { Time.now}
|
||||
trait :rejected do
|
||||
rejected_at Time.now
|
||||
end
|
||||
end
|
||||
|
||||
factory :tag, class: 'ActsAsTaggableOn::Tag' do
|
||||
@@ -82,4 +102,5 @@ FactoryGirl.define do
|
||||
id { SecureRandom.uuid }
|
||||
started_at DateTime.now
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,61 +2,64 @@ require 'rails_helper'
|
||||
|
||||
feature 'Moderate Comments' do
|
||||
|
||||
scenario 'Hide', :js do
|
||||
citizen = create(:user)
|
||||
moderator = create(:moderator)
|
||||
feature 'Hiding Comments' do
|
||||
|
||||
debate = create(:debate)
|
||||
comment = create(:comment, commentable: debate, body: 'SPAM')
|
||||
scenario 'Hide', :js do
|
||||
citizen = create(:user)
|
||||
moderator = create(:moderator)
|
||||
|
||||
login_as(moderator.user)
|
||||
visit debate_path(debate)
|
||||
debate = create(:debate)
|
||||
comment = create(:comment, commentable: debate, body: 'SPAM')
|
||||
|
||||
within("#comment_#{comment.id}") do
|
||||
click_link 'Hide'
|
||||
expect(page).to have_css('.comment .faded')
|
||||
login_as(moderator.user)
|
||||
visit debate_path(debate)
|
||||
|
||||
within("#comment_#{comment.id}") do
|
||||
click_link 'Hide'
|
||||
expect(page).to have_css('.comment .faded')
|
||||
end
|
||||
|
||||
login_as(citizen)
|
||||
visit debate_path(debate)
|
||||
|
||||
expect(page).to have_css('.comment', count: 1)
|
||||
expect(page).to have_content('This comment has been deleted')
|
||||
expect(page).to_not have_content('SPAM')
|
||||
end
|
||||
|
||||
login_as(citizen)
|
||||
visit debate_path(debate)
|
||||
scenario 'Children visible', :js do
|
||||
citizen = create(:user)
|
||||
moderator = create(:moderator)
|
||||
|
||||
expect(page).to have_css('.comment', count: 1)
|
||||
expect(page).to have_content('This comment has been deleted')
|
||||
expect(page).to_not have_content('SPAM')
|
||||
end
|
||||
debate = create(:debate)
|
||||
comment = create(:comment, commentable: debate, body: 'SPAM')
|
||||
create(:comment, commentable: debate, body: 'Acceptable reply', parent_id: comment.id)
|
||||
|
||||
scenario 'Children visible', :js do
|
||||
citizen = create(:user)
|
||||
moderator = create(:moderator)
|
||||
login_as(moderator.user)
|
||||
visit debate_path(debate)
|
||||
|
||||
debate = create(:debate)
|
||||
comment = create(:comment, commentable: debate, body: 'SPAM')
|
||||
reply = create(:comment, commentable: debate, body: 'Acceptable reply', parent_id: comment.id)
|
||||
within("#comment_#{comment.id}") do
|
||||
first(:link, "Hide").click
|
||||
expect(page).to have_css('.comment .faded')
|
||||
end
|
||||
|
||||
login_as(moderator.user)
|
||||
visit debate_path(debate)
|
||||
login_as(citizen)
|
||||
visit debate_path(debate)
|
||||
|
||||
within("#comment_#{comment.id}") do
|
||||
first(:link, "Hide").click
|
||||
expect(page).to have_css('.comment .faded')
|
||||
expect(page).to have_css('.comment', count: 2)
|
||||
expect(page).to have_content('This comment has been deleted')
|
||||
expect(page).to_not have_content('SPAM')
|
||||
|
||||
expect(page).to have_content('Acceptable reply')
|
||||
end
|
||||
|
||||
login_as(citizen)
|
||||
visit debate_path(debate)
|
||||
|
||||
expect(page).to have_css('.comment', count: 2)
|
||||
expect(page).to have_content('This comment has been deleted')
|
||||
expect(page).to_not have_content('SPAM')
|
||||
|
||||
expect(page).to have_content('Acceptable reply')
|
||||
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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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,13 +124,41 @@ describe Ability do
|
||||
it { should be_able_to( :verify, rejected_organization) }
|
||||
end
|
||||
|
||||
it { should be_able_to(:hide, comment) }
|
||||
it { should be_able_to(:hide, debate) }
|
||||
it { should be_able_to(:hide, other_user) }
|
||||
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_not be_able_to(:restore, comment) }
|
||||
it { should_not be_able_to(:restore, debate) }
|
||||
it { should_not be_able_to(:restore, other_user) }
|
||||
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
|
||||
|
||||
64
spec/models/inappropiate_flag_spec.rb
Normal file
64
spec/models/inappropiate_flag_spec.rb
Normal 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
|
||||
Reference in New Issue
Block a user