Commit fb968957 by Kevin Chugh

Merge pull request #13 from rll/feature/kevin/flagging

Feature/kevin/flagging
parents ac7a0a32 585a6521
...@@ -30,7 +30,6 @@ benchmark_log ...@@ -30,7 +30,6 @@ benchmark_log
bin/ bin/
log/ log/
#redcar #redcar
.redcar/ .redcar/
/nbproject /nbproject
\ No newline at end of file
...@@ -156,7 +156,7 @@ namespace :db do ...@@ -156,7 +156,7 @@ namespace :db do
"comment_count" => 0, "title" => Faker::Lorem.sentence(6), "author_id" => rand(1..10).to_s, "comment_count" => 0, "title" => Faker::Lorem.sentence(6), "author_id" => rand(1..10).to_s,
"body" => Faker::Lorem.paragraphs.join("\n\n"), "course_id" => COURSE_ID, "created_at" => Time.now, "body" => Faker::Lorem.paragraphs.join("\n\n"), "course_id" => COURSE_ID, "created_at" => Time.now,
"commentable_id" => COURSE_ID, "closed" => [true, false].sample, "updated_at" => Time.now, "last_activity_at" => Time.now, "commentable_id" => COURSE_ID, "closed" => [true, false].sample, "updated_at" => Time.now, "last_activity_at" => Time.now,
"votes" => {"count" => 0, "down" => [], "down_count" => 0, "point" => 0, "up" => [], "up_count" => []}} "votes" => {"count" => 0, "down" => [], "down_count" => 0, "point" => 0, "up" => [], "up_count" => [], "spoiler_count" => []}}
coll.insert(doc) coll.insert(doc)
end end
binding.pry binding.pry
......
put "#{APIPREFIX}/threads/:thread_id/abuse_flags" do |thread_id|
flag_as_abuse thread
end
put "#{APIPREFIX}/threads/:thread_id/abuse_unflags" do |thread_id|
un_flag_as_abuse thread
end
put "#{APIPREFIX}/comments/:comment_id/abuse_flags" do |thread_id|
flag_as_abuse comment
end
put "#{APIPREFIX}/comments/:comment_id/abuse_unflags" do |thread_id|
un_flag_as_abuse comment
end
\ No newline at end of file
...@@ -60,6 +60,7 @@ require './api/comment_threads' ...@@ -60,6 +60,7 @@ require './api/comment_threads'
require './api/comments' require './api/comments'
require './api/users' require './api/users'
require './api/votes' require './api/votes'
require './api/flags'
require './api/pins' require './api/pins'
require './api/notifications_and_subscriptions' require './api/notifications_and_subscriptions'
......
...@@ -7,7 +7,7 @@ helpers do ...@@ -7,7 +7,7 @@ helpers do
raise ArgumentError, "User id is required" unless @user || params[:user_id] raise ArgumentError, "User id is required" unless @user || params[:user_id]
@user ||= User.find_by(external_id: params[:user_id]) @user ||= User.find_by(external_id: params[:user_id])
end end
def thread def thread
@thread ||= CommentThread.find(params[:thread_id]) @thread ||= CommentThread.find(params[:thread_id])
end end
...@@ -37,11 +37,33 @@ helpers do ...@@ -37,11 +37,33 @@ helpers do
obj.reload.to_hash.to_json obj.reload.to_hash.to_json
end end
def flag_as_abuse(obj)
raise ArgumentError, "User id is required" unless user
obj.abuse_flaggers << user.id unless obj.abuse_flaggers.include? user.id
obj.save
obj.reload.to_hash.to_json
end
def un_flag_as_abuse(obj)
raise ArgumentError, "User id is required" unless user
if params["all"]
obj.historical_abuse_flaggers += obj.abuse_flaggers
obj.historical_abuse_flaggers = obj.historical_abuse_flaggers.uniq
obj.abuse_flaggers.clear
else
obj.abuse_flaggers.delete user.id
end
obj.save
obj.reload.to_hash.to_json
end
def undo_vote_for(obj) def undo_vote_for(obj)
raise ArgumentError, "must provide user id" unless user raise ArgumentError, "must provide user id" unless user
user.unvote(obj) user.unvote(obj)
obj.reload.to_hash.to_json obj.reload.to_hash.to_json
end end
def pin(obj) def pin(obj)
raise ArgumentError, "User id is required" unless user raise ArgumentError, "User id is required" unless user
...@@ -92,7 +114,26 @@ helpers do ...@@ -92,7 +114,26 @@ helpers do
end end
def handle_threads_query(comment_threads) def handle_threads_query(comment_threads)
comment_threads = comment_threads.where(:course_id=>params[:course_id])
if params[:course_id]
comment_threads = comment_threads.where(:course_id=>params[:course_id])
if params[:flagged]
#get flagged threads and threads containing flagged responses
comment_ids = Comment.where(:course_id=>params[:course_id]).
where(:abuse_flaggers.ne => [],:abuse_flaggers.exists => true).
collect{|c| c.comment_thread_id}.uniq
thread_ids = comment_threads.where(:abuse_flaggers.ne => [],:abuse_flaggers.exists => true).
collect{|c| c.id}
comment_ids += thread_ids
comment_threads = comment_threads.where(:id.in => comment_ids)
end
end
if CommentService.config[:cache_enabled] if CommentService.config[:cache_enabled]
query_params = params.slice(*%w[course_id commentable_id sort_key sort_order page per_page user_id]) query_params = params.slice(*%w[course_id commentable_id sort_key sort_order page per_page user_id])
memcached_key = "threads_query_#{query_params.hash}" memcached_key = "threads_query_#{query_params.hash}"
...@@ -141,6 +182,22 @@ helpers do ...@@ -141,6 +182,22 @@ helpers do
} }
Sinatra::Application.cache.set(memcached_key, cached_results, CommentService.config[:cache_timeout][:threads_query].to_i) Sinatra::Application.cache.set(memcached_key, cached_results, CommentService.config[:cache_timeout][:threads_query].to_i)
end end
#now we have to walk through the flagged threads and comments and add the user to the
#abuse flaggers so that the flag appears for them.
#this will allow moderators to see all flagged posts
comment_threads.each do |t|
if t.abuse_flaggers and t.abuse_flaggers.count > 0
t.abuse_flaggers << params["user_id"]
end
t.comments.each do |c|
if c.abuse_flaggers and c.abuse_flaggers.count > 0
c.abuse_flaggers << params["user_id"]
end
end
end
{ {
collection: paged_comment_threads.map{|t| t.to_hash(recursive: bool_recursive, user_id: params["user_id"])}, collection: paged_comment_threads.map{|t| t.to_hash(recursive: bool_recursive, user_id: params["user_id"])},
num_pages: num_pages, num_pages: num_pages,
...@@ -153,8 +210,8 @@ helpers do ...@@ -153,8 +210,8 @@ helpers do
contents.map do |content| contents.map do |content|
content['children'] = author_contents_only(content['children'], author_id) content['children'] = author_contents_only(content['children'], author_id)
if content['children'].length > 0 or \ if content['children'].length > 0 or \
(content['user_id'] == author_id and not content['anonymous'] and not content['anonymous_to_peers']) (content['user_id'] == author_id and not content['anonymous'] and not content['anonymous_to_peers'])
content content
else else
nil nil
end end
......
require 'rest_client'
roots = {}
roots['development'] = "http://localhost:8000"
roots['test'] = "http://localhost:8000"
roots['production'] = "http://edx.org"
ROOT = roots[ENV['SINATRA_ENV']]
namespace :flags do
#USAGE
#SINATRA_ENV=development rake flags:flagged
task :flagged => :environment do
flagged = Content.flagged
courses = {}
flagged.each do |f|
if not courses[f.course_id]
courses[f.course_id] = []
end
courses[f.course_id] << f
end
courses.each do |k,v|
puts "#{k.upcase}"
puts "****************"
v.each do |f|
puts "#{ROOT}/courses/#{f.course_id}/discussion/forum/#{f.commentable_id}/threads/#{f.comment_thread_id} (#{f.class})"
end
puts "\n\n\n\n"
end
end
end
...@@ -60,10 +60,20 @@ class Comment < Content ...@@ -60,10 +60,20 @@ class Comment < Content
.merge("thread_id" => comment_thread.id) .merge("thread_id" => comment_thread.id)
.merge("commentable_id" => comment_thread.commentable_id) .merge("commentable_id" => comment_thread.commentable_id)
.merge("votes" => votes.slice(*%w[count up_count down_count point])) .merge("votes" => votes.slice(*%w[count up_count down_count point]))
.merge("abuse_flaggers" => abuse_flaggers)
.merge("type" => "comment") .merge("type" => "comment")
end end
end end
def commentable_id
#we need this to have a universal access point for the flag rake task
if self.comment_thread_id
t = CommentThread.find self.comment_thread_id
if t
t.commentable_id
end
end
end
private private
def set_thread_last_activity_at def set_thread_last_activity_at
......
...@@ -175,6 +175,7 @@ class CommentThread < Content ...@@ -175,6 +175,7 @@ class CommentThread < Content
.merge("id" => _id, "user_id" => author.id, .merge("id" => _id, "user_id" => author.id,
"username" => author.username, "username" => author.username,
"votes" => votes.slice(*%w[count up_count down_count point]), "votes" => votes.slice(*%w[count up_count down_count point]),
"abuse_flaggers" => abuse_flaggers,
"tags" => tags_array, "tags" => tags_array,
"type" => "thread", "type" => "thread",
"group_id" => group_id, "group_id" => group_id,
...@@ -227,6 +228,11 @@ class CommentThread < Content ...@@ -227,6 +228,11 @@ class CommentThread < Content
!!(tag =~ RE_TAG) !!(tag =~ RE_TAG)
end end
def comment_thread_id
#so that we can use the comment thread id as a common attribute for flagging
self.id
end
private private
RE_HEADCHAR = /[a-z0-9]/ RE_HEADCHAR = /[a-z0-9]/
...@@ -255,4 +261,5 @@ private ...@@ -255,4 +261,5 @@ private
def destroy_subscriptions def destroy_subscriptions
subscriptions.delete_all subscriptions.delete_all
end end
end end
class Content class Content
include Mongoid::Document include Mongoid::Document
field :visible, type: Boolean, default: true
field :abuse_flaggers, type: Array, default: []
field :historical_abuse_flaggers, type: Array, default: [] #preserve abuse flaggers after a moderator unflags
def author_with_anonymity(attr=nil, attr_when_anonymous=nil) def author_with_anonymity(attr=nil, attr_when_anonymous=nil)
if not attr if not attr
(anonymous || anonymous_to_peers) ? nil : author (anonymous || anonymous_to_peers) ? nil : author
...@@ -9,6 +13,15 @@ class Content ...@@ -9,6 +13,15 @@ class Content
(anonymous || anonymous_to_peers) ? attr_when_anonymous : author.send(attr) (anonymous || anonymous_to_peers) ? attr_when_anonymous : author.send(attr)
end end
end end
def self.flagged
#return an array of flagged content
holder = []
Content.where(:abuse_flaggers.ne => [],:abuse_flaggers.exists => true).each do |c|
holder << c
end
holder
end
def self.prolific_metric what, count def self.prolific_metric what, count
#take a hash of criteria (what) and return a hash of hashes #take a hash of criteria (what) and return a hash of hashes
...@@ -70,7 +83,5 @@ class Content ...@@ -70,7 +83,5 @@ class Content
answer answer
end end
end end
require 'spec_helper'
def create_comment_flag(comment_id, user_id)
create_flag("/api/v1/comments/" + comment_id + "/abuse_flags", user_id)
end
def create_thread_flag(thread_id, user_id)
create_flag("/api/v1/threads/" + thread_id + "/abuse_flags", user_id)
end
def remove_thread_flag(thread_id, user_id)
remove_flag("/api/v1/threads/" + thread_id + "/abuse_unflags", user_id)
end
def remove_comment_flag(comment_id, user_id)
remove_flag("/api/v1/comments/" + comment_id + "/abuse_unflags", user_id)
end
def create_flag(put_command, user_id)
if user_id.nil?
put put_command
else
put put_command, user_id: user_id
end
end
def remove_flag(put_command, user_id)
if user_id.nil?
put put_command
else
put put_command, user_id: user_id
end
end
describe "app" do
describe "abuse" do
before(:each) { init_without_subscriptions }
describe "flag a comment as abusive" do
it "create or update the abuse_flags on the comment" do
comment = Comment.first
prev_abuse_flaggers = comment.abuse_flaggers
create_comment_flag("#{comment.id}", User.first.id)
comment = Comment.find(comment.id)
comment.abuse_flaggers.length.should == prev_abuse_flaggers.length + 1
# verify that the thread doesn't automatically get flagged
comment.comment_thread.abuse_flaggers.length.should == 0
end
it "returns 400 when the comment does not exist" do
create_comment_flag("does_not_exist", User.first.id)
last_response.status.should == 400
end
it "returns 400 when user_id is not provided" do
create_comment_flag("#{Comment.first.id}", nil)
last_response.status.should == 400
end
#Would like to test the output of to_hash, but not sure how to deal with a Moped::BSON::Document object
#it "has a correct hash" do
# create_flag("#{Comment.first.id}", User.first.id)
# Comment.first.to_hash
#end
end
describe "flag a thread as abusive" do
it "create or update the abuse_flags on the comment" do
comment = Comment.first
thread = comment.comment_thread
prev_abuse_flaggers = thread.abuse_flaggers
create_thread_flag("#{thread.id}", User.first.id)
comment = Comment.find(comment.id)
comment.comment_thread.abuse_flaggers.length.should == prev_abuse_flaggers.length + 1
# verify that the comment doesn't automatically get flagged
comment.abuse_flaggers.length.should == 0
end
it "returns 400 when the thread does not exist" do
create_thread_flag("does_not_exist", User.first.id)
last_response.status.should == 400
end
it "returns 400 when user_id is not provided" do
create_thread_flag("#{Comment.first.comment_thread.id}", nil)
last_response.status.should == 400
end
#Would like to test the output of to_hash, but not sure how to deal with a Moped::BSON::Document object
#it "has a correct hash" do
# create_thread_flag("#{Comment.first.comment_thread.id}", User.first.id)
# Comment.first.comment_thread.to_hash
#end
end
describe "unflag a comment as abusive" do
it "removes the user from the existing abuse_flaggers" do
comment = Comment.first
create_comment_flag("#{comment.id}", User.first.id)
comment = Comment.first
prev_abuse_flaggers = comment.abuse_flaggers
prev_abuse_flaggers.should include User.first.id
remove_comment_flag("#{comment.id}", User.first.id)
comment = Comment.find(comment.id)
comment.abuse_flaggers.length.should == prev_abuse_flaggers.length - 1
comment.abuse_flaggers.to_a.should_not include User.first.id
end
it "returns 400 when the thread does not exist" do
remove_thread_flag("does_not_exist", User.first.id)
last_response.status.should == 400
end
it "returns 400 when user_id is not provided" do
remove_thread_flag("#{Comment.first.comment_thread.id}", nil)
last_response.status.should == 400
end
#Would like to test the output of to_hash, but not sure how to deal with a Moped::BSON::Document object
#it "has a correct hash" do
# create_thread_flag("#{Comment.first.comment_thread.id}", User.first.id)
# Comment.first.comment_thread.to_hash
#end
end
end
end
...@@ -62,7 +62,7 @@ describe "app" do ...@@ -62,7 +62,7 @@ describe "app" do
changed_thread = CommentThread.find(thread.id) changed_thread = CommentThread.find(thread.id)
changed_thread.body.should == "new body" changed_thread.body.should == "new body"
changed_thread.title.should == "new title" changed_thread.title.should == "new title"
changed_thread.commentable_id.should == "new commentable_id" changed_thread.commentable_id.should == "new_commentable_id"
end end
it "returns 400 when the thread does not exist" do it "returns 400 when the thread does not exist" do
put "/api/v1/threads/does_not_exist", body: "new body", title: "new title" put "/api/v1/threads/does_not_exist", body: "new body", title: "new title"
......
...@@ -20,6 +20,9 @@ end ...@@ -20,6 +20,9 @@ end
RSpec.configure do |config| RSpec.configure do |config|
config.include Rack::Test::Methods config.include Rack::Test::Methods
config.treat_symbols_as_metadata_keys_with_true_values = true
config.filter_run focus: true
config.run_all_when_everything_filtered = true
end end
Mongoid.configure do |config| Mongoid.configure do |config|
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment