diff --git a/app/controllers/admin/organizations_controller.rb b/app/controllers/admin/organizations_controller.rb index 19003ad3a..de99bbc44 100644 --- a/app/controllers/admin/organizations_controller.rb +++ b/app/controllers/admin/organizations_controller.rb @@ -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 diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index a10d80da9..20df3bf2d 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -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 diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index c30f02233..f8e7b968a 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -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 diff --git a/app/controllers/moderation/comments_controller.rb b/app/controllers/moderation/comments_controller.rb index c250fc460..03667e286 100644 --- a/app/controllers/moderation/comments_controller.rb +++ b/app/controllers/moderation/comments_controller.rb @@ -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 -end \ No newline at end of file + 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 diff --git a/app/controllers/moderation/debates_controller.rb b/app/controllers/moderation/debates_controller.rb index 622116765..22e4eac6b 100644 --- a/app/controllers/moderation/debates_controller.rb +++ b/app/controllers/moderation/debates_controller.rb @@ -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 -end \ No newline at end of file + 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 diff --git a/app/models/ability.rb b/app/models/ability.rb index 10a136e22..9032038a1 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -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? diff --git a/app/models/comment.rb b/app/models/comment.rb index 0c298182d..7c18efc6b 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -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 diff --git a/app/models/debate.rb b/app/models/debate.rb index 4d9b4ffe8..c16048f70 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -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 diff --git a/app/models/inappropiate_flag.rb b/app/models/inappropiate_flag.rb new file mode 100644 index 000000000..b60b98618 --- /dev/null +++ b/app/models/inappropiate_flag.rb @@ -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 diff --git a/app/models/user.rb b/app/models/user.rb index 4a93f8cf1..544142c2f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -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} diff --git a/app/views/comments/_comment.html.erb b/app/views/comments/_comment.html.erb index 16aa2e73c..0d922be04 100644 --- a/app/views/comments/_comment.html.erb +++ b/app/views/comments/_comment.html.erb @@ -41,6 +41,11 @@ <% end %>  • <%= time_ago_in_words(comment.created_at) %> + + + <%= render 'comments/flag_as_inappropiate_actions', comment: comment %> + + <% if comment.user.official? && comment.user_id == @debate.author_id %> diff --git a/app/views/comments/_flag_as_inappropiate_actions.html.erb b/app/views/comments/_flag_as_inappropiate_actions.html.erb new file mode 100644 index 000000000..fad83d4d6 --- /dev/null +++ b/app/views/comments/_flag_as_inappropiate_actions.html.erb @@ -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 %> diff --git a/app/views/comments/_refresh_flag_as_inappropiate_actions.js.erb b/app/views/comments/_refresh_flag_as_inappropiate_actions.js.erb new file mode 100644 index 000000000..58f356f2b --- /dev/null +++ b/app/views/comments/_refresh_flag_as_inappropiate_actions.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@comment) %> .js-flag-as-inappropiate-actions").html('<%= j render("comments/flag_as_inappropiate_actions", comment: @comment) %>'); diff --git a/app/views/debates/_flag_as_inappropiate_actions.html.erb b/app/views/debates/_flag_as_inappropiate_actions.html.erb new file mode 100644 index 000000000..8003600e0 --- /dev/null +++ b/app/views/debates/_flag_as_inappropiate_actions.html.erb @@ -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 %> diff --git a/app/views/debates/_refresh_flag_as_inappropiate_actions.js.erb b/app/views/debates/_refresh_flag_as_inappropiate_actions.js.erb new file mode 100644 index 000000000..7be008e35 --- /dev/null +++ b/app/views/debates/_refresh_flag_as_inappropiate_actions.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@debate) %> .js-flag-as-inappropiate-actions").html('<%= j render("debates/flag_as_inappropiate_actions", debate: @debate) %>'); diff --git a/app/views/debates/show.html.erb b/app/views/debates/show.html.erb index 1475e95a8..2c69e79f7 100644 --- a/app/views/debates/show.html.erb +++ b/app/views/debates/show.html.erb @@ -41,6 +41,10 @@  •    <%= link_to t("debates.show.comments", count: @debate.comment_threads.count), "#comments" %> + + + <%= render 'debates/flag_as_inappropiate_actions', debate: @debate %> + <%= @debate.description %> diff --git a/app/views/moderation/_menu.html.erb b/app/views/moderation/_menu.html.erb index 21997abca..41e84287a 100644 --- a/app/views/moderation/_menu.html.erb +++ b/app/views/moderation/_menu.html.erb @@ -1,3 +1,21 @@ -
- Moderation links -
\ No newline at end of file + diff --git a/app/views/moderation/comments/index.html.erb b/app/views/moderation/comments/index.html.erb new file mode 100644 index 000000000..0d89114f1 --- /dev/null +++ b/app/views/moderation/comments/index.html.erb @@ -0,0 +1,50 @@ +

<%= t('moderation.comments.index.title') %>

+ +

+ <%= 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 %> +

+ +

<%= page_entries_info @comments %>

+ + + + + + + + + + <% @comments.each do |comment| %> + + + + + + + + <% if can? :mark_as_reviewed, comment %> + + <% end %> + <% if comment.reviewed? %> + + <% end %> + + <% end %> +
<%= t('moderation.comments.index.headers.flags') %><%= t('moderation.comments.index.headers.updated_at') %><%= t('moderation.comments.index.headers.commentable_type') %><%= t('moderation.comments.index.headers.commentable') %><%= t('moderation.comments.index.headers.comment') %>
<%= comment.inappropiate_flags_count %><%= l comment.updated_at.to_date %><%= comment.commentable_type.constantize.model_name.human %><%= link_to comment.commentable.title, comment.commentable %><%= comment.body %> + <%= link_to t('moderation.comments.index.hide'), hide_in_moderation_screen_moderation_comment_path(comment, request.query_parameters), method: :put %> + + <%= link_to t('moderation.comments.index.mark_as_reviewed'), mark_as_reviewed_moderation_comment_path(comment, request.query_parameters), method: :put %> + + <%= t('moderation.comments.index.reviewed') %> +
+ +<%= paginate @comments %> + diff --git a/app/views/moderation/debates/index.html.erb b/app/views/moderation/debates/index.html.erb new file mode 100644 index 000000000..611f68e16 --- /dev/null +++ b/app/views/moderation/debates/index.html.erb @@ -0,0 +1,48 @@ +

<%= t('moderation.debates.index.title') %>

+ +

+ <%= 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 %> +

+ +

<%= page_entries_info @debates %>

+ + + + + + + + + <% @debates.each do |debate| %> + + + + + + + <% if can? :mark_as_reviewed, debate %> + + <% end %> + <% if debate.reviewed? %> + + <% end %> + + <% end %> +
<%= t('moderation.debates.index.headers.flags') %><%= t('moderation.debates.index.headers.updated_at') %><%= t('moderation.debates.index.headers.title') %><%= t('moderation.debates.index.headers.description') %>
<%= debate.inappropiate_flags_count %><%= l debate.updated_at.to_date %><%= link_to debate.title, debate %><%= debate.description %> + <%= link_to t('moderation.debates.index.hide'), hide_in_moderation_screen_moderation_debate_path(debate, request.query_parameters), method: :put %> + + <%= link_to t('moderation.debates.index.mark_as_reviewed'), mark_as_reviewed_moderation_debate_path(debate, request.query_parameters), method: :put %> + + <%= t('moderation.debates.index.reviewed') %> +
+ +<%= paginate @debates %> + diff --git a/app/views/shared/_admin_login_items.html.erb b/app/views/shared/_admin_login_items.html.erb index e268def4b..1d22679c5 100644 --- a/app/views/shared/_admin_login_items.html.erb +++ b/app/views/shared/_admin_login_items.html.erb @@ -6,10 +6,10 @@ <% end %> - <% if current_user.moderator? %> + <% if current_user.moderator? || current_user.administrator? %>
  • <%= link_to t("layouts.header.moderation"), moderation_root_path %>
  • <% end %> <% end %> - \ No newline at end of file + diff --git a/config/locales/en.yml b/config/locales/en.yml index 1f1b28c5c..b47d06c44 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -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: diff --git a/config/locales/es.yml b/config/locales/es.yml index cb7fed993..ef903082f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -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: diff --git a/config/locales/moderation.en.yml b/config/locales/moderation.en.yml index cf53051ed..2728f10d9 100644 --- a/config/locales/moderation.en.yml +++ b/config/locales/moderation.en.yml @@ -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 diff --git a/config/locales/moderation.es.yml b/config/locales/moderation.es.yml index a921f0827..2afaec7a9 100644 --- a/config/locales/moderation.es.yml +++ b/config/locales/moderation.es.yml @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 7dbe31613..f224609b9 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -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 diff --git a/db/migrate/20150820103351_create_inappropiate_flags.rb b/db/migrate/20150820103351_create_inappropiate_flags.rb new file mode 100644 index 000000000..982d9f705 --- /dev/null +++ b/db/migrate/20150820103351_create_inappropiate_flags.rb @@ -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 diff --git a/db/migrate/20150820103800_add_inappropiate_flag_fields_to_comments.rb b/db/migrate/20150820103800_add_inappropiate_flag_fields_to_comments.rb new file mode 100644 index 000000000..92a53581b --- /dev/null +++ b/db/migrate/20150820103800_add_inappropiate_flag_fields_to_comments.rb @@ -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 diff --git a/db/migrate/20150820104552_add_inappropiate_flag_fields_to_debates.rb b/db/migrate/20150820104552_add_inappropiate_flag_fields_to_debates.rb new file mode 100644 index 000000000..c5376ed62 --- /dev/null +++ b/db/migrate/20150820104552_add_inappropiate_flag_fields_to_debates.rb @@ -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 diff --git a/db/migrate/20150821180131_add_reviewed_at_to_comments.rb b/db/migrate/20150821180131_add_reviewed_at_to_comments.rb new file mode 100644 index 000000000..b854002ac --- /dev/null +++ b/db/migrate/20150821180131_add_reviewed_at_to_comments.rb @@ -0,0 +1,5 @@ +class AddReviewedAtToComments < ActiveRecord::Migration + def change + add_column :comments, :reviewed_at, :datetime + end +end diff --git a/db/migrate/20150821180155_add_reviewed_at_to_debates.rb b/db/migrate/20150821180155_add_reviewed_at_to_debates.rb new file mode 100644 index 000000000..c02ed3e3b --- /dev/null +++ b/db/migrate/20150821180155_add_reviewed_at_to_debates.rb @@ -0,0 +1,5 @@ +class AddReviewedAtToDebates < ActiveRecord::Migration + def change + add_column :debates, :reviewed_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 3cd1b55d1..040d21d10 100644 --- a/db/schema.rb +++ b/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 diff --git a/spec/factories.rb b/spec/factories.rb index 101ce2746..038a0102e 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -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 diff --git a/spec/features/admin/organizations_spec.rb b/spec/features/admin/organizations_spec.rb index 954e2ee8f..d13758117 100644 --- a/spec/features/admin/organizations_spec.rb +++ b/spec/features/admin/organizations_spec.rb @@ -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) diff --git a/spec/features/comments_spec.rb b/spec/features/comments_spec.rb index c7f2178bf..10a919a07 100644 --- a/spec/features/comments_spec.rb +++ b/spec/features/comments_spec.rb @@ -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 diff --git a/spec/features/debates_spec.rb b/spec/features/debates_spec.rb index eade338f5..c685725d8 100644 --- a/spec/features/debates_spec.rb +++ b/spec/features/debates_spec.rb @@ -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 diff --git a/spec/features/moderation/comments_spec.rb b/spec/features/moderation/comments_spec.rb index 099946d82..e52a87c17 100644 --- a/spec/features/moderation/comments_spec.rb +++ b/spec/features/moderation/comments_spec.rb @@ -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 -end \ No newline at end of file + 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 diff --git a/spec/features/moderation/debates_spec.rb b/spec/features/moderation/debates_spec.rb index 5692a8b5b..bb2b58c24 100644 --- a/spec/features/moderation/debates_spec.rb +++ b/spec/features/moderation/debates_spec.rb @@ -23,4 +23,113 @@ feature 'Moderate debates' do expect(page).to have_css('.debate', count: 0) end -end \ No newline at end of file + 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 diff --git a/spec/features/moderation_spec.rb b/spec/features/moderation_spec.rb index cd3052f70..78df5e56f 100644 --- a/spec/features/moderation_spec.rb +++ b/spec/features/moderation_spec.rb @@ -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" diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index b484e9e7c..1ff4933b0 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -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 diff --git a/spec/models/inappropiate_flag_spec.rb b/spec/models/inappropiate_flag_spec.rb new file mode 100644 index 000000000..0ae831ee0 --- /dev/null +++ b/spec/models/inappropiate_flag_spec.rb @@ -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