Skip to content

Commit

Permalink
add moderation logging
Browse files Browse the repository at this point in the history
- add users.is_moderator and look at that for most things, not
is_admin

- make default user in readme be a moderator

- log moderator actions in story edits, comment
  deletions/undeletions (and later, user disabling).

- remove ability for moderators to edit comments, there's really no
  reason to.
  • Loading branch information
jcs committed Sep 2, 2012
1 parent 4692cf6 commit e6c74e8
Show file tree
Hide file tree
Showing 18 changed files with 223 additions and 45 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ This is the source code to the site operating at [https://lobste.rs](https://lob
Loading development environment (Rails 3.2.6)
irb(main):001:0> u = User.new(:username => "test", :email => "test@example.com", :password => "test")
irb(main):002:0> u.is_admin = true
irb(main):002:0> u.is_moderator = true
irb(main):003:0> u.save

irb(main):004:0> t = Tag.new
Expand Down
3 changes: 2 additions & 1 deletion app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,8 @@ div#story_preview {
div#story_box input#story_url {
width: 508px;
}
div#story_box input#story_title {
div#story_box input#story_title,
div#story_box input#story_moderation_reason {
width: 600px;
}
div#story_box #story_tags_a {
Expand Down
7 changes: 3 additions & 4 deletions app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def edit

def delete
if !((comment = Comment.find_by_short_id(params[:comment_id])) &&
comment.is_editable_by_user?(@user))
comment.is_deletable_by_user?(@user))
return render :text => "can't find comment", :status => 400
end

Expand Down Expand Up @@ -175,9 +175,8 @@ def index
@title = "Newest Comments"
@cur_url = "/comments"

@comments = Comment.find(:all, :conditions => "is_deleted = 0 AND " +
"is_moderated = 0", :order => "created_at DESC", :limit => 20,
:include => [ :user, :story ])
@comments = Comment.find(:all, :conditions => "is_deleted = 0",
:order => "created_at DESC", :limit => 20, :include => [ :user, :story ])

if @user
@votes = Vote.comment_votes_by_user_for_comment_ids_hash(@user.id,
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/home_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def find_stories_for_user_and_tag_and_newest(user, tag = nil, newest = false)
end

def _find_stories_for_user_and_tag_and_newest(user, tag = nil, newest = false)
conds = [ "is_expired = 0 AND is_moderated = 0 " ]
conds = [ "is_expired = 0 " ]

if user && !newest
# exclude downvoted items
Expand Down
13 changes: 13 additions & 0 deletions app/controllers/moderations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class ModerationsController < ApplicationController
def index
@pages = Moderation.count
@page = params[:page] ? params[:page].to_i : 0

if @page < 1
@page = 1
end

@moderations = Moderation.order("id desc").limit(50).offset((@page - 1) *
50).all
end
end
13 changes: 7 additions & 6 deletions app/controllers/stories_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ def destroy
return redirect_to "/"
end

if @user.is_admin? && @user.id != @story.user_id
@story.is_moderated = true
else
@story.is_expired = true
@story.is_expired = true

if @user.is_moderator? && @user.id != @story.user_id
@story.editor_user_id = @user.id
end

@story.save(:validate => false)
Expand Down Expand Up @@ -184,7 +184,7 @@ def undelete
end

@story.is_expired = false
@story.is_moderated = false
@story.editor_user_id = @user.id
@story.save(:validate => false)

redirect_to @story.comments_url
Expand All @@ -197,6 +197,7 @@ def update
end

@story.is_expired = false
@story.editor_user_id = @user.id

if @story.update_attributes(params[:story].except(:url))
return redirect_to @story.comments_url
Expand Down Expand Up @@ -244,7 +245,7 @@ def downvote

private
def find_story
if @user.is_admin?
if @user.is_moderator?
@story = Story.find_by_short_id(params[:story_id] || params[:id])
else
@story = Story.find_by_user_id_and_short_id(@user.id,
Expand Down
49 changes: 36 additions & 13 deletions app/models/comment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class Comment < ActiveRecord::Base
belongs_to :parent_comment,
:class_name => "Comment"

attr_accessible :comment
attr_accessible :comment, :moderation_reason

attr_accessor :parent_comment_short_id, :current_vote, :previewing,
:indent_level, :highlighted
Expand All @@ -24,10 +24,10 @@ class Comment < ActiveRecord::Base
has "(upvotes - downvotes)", :as => :score, :type => :integer,
:sortable => true

has is_moderated, is_deleted
has is_deleted
has created_at

where "is_moderated = 0 AND is_deleted = 0"
where "is_deleted = 0"
end

validate do
Expand Down Expand Up @@ -81,7 +81,7 @@ def assign_votes
end

def is_gone?
is_deleted? || is_moderated?
is_deleted?
end

def mark_submitter
Expand Down Expand Up @@ -112,10 +112,16 @@ def deliver_reply_notifications
def delete_for_user(user)
Comment.record_timestamps = false

if user.is_admin? && user.id != self.user_id
self.is_deleted = true

if user.is_moderator? && user.id != self.user_id
self.is_moderated = true
else
self.is_deleted = true

m = Moderation.new
m.comment_id = self.id
m.moderator_user_id = user.id
m.action = "deleted comment"
m.save
end

self.save(:validate => false)
Expand All @@ -127,9 +133,18 @@ def delete_for_user(user)
def undelete_for_user(user)
Comment.record_timestamps = false

self.is_moderated = false
self.is_deleted = false

if user.is_moderator? && user.id != self.user_id
self.is_moderated = true

m = Moderation.new
m.comment_id = self.id
m.moderator_user_id = user.id
m.action = "undeleted comment"
m.save
end

self.save(:validate => false)
Comment.record_timestamps = true

Expand Down Expand Up @@ -233,7 +248,7 @@ def self.ordered_for_story_or_thread_for_user(story_id, thread_id, user)
if c.is_gone?
if ordered[x + 1] && (ordered[x + 1].indent_level > c.indent_level)
# we have child comments, so we must stay
elsif user && (user.is_admin? || c.user_id == user.id)
elsif user && (user.is_moderator? || c.user_id == user.id)
# admins and authors should be able to see their deleted comments
else
# drop this one
Expand All @@ -248,9 +263,7 @@ def self.ordered_for_story_or_thread_for_user(story_id, thread_id, user)
end

def is_editable_by_user?(user)
if user && user.is_admin?
return true
elsif user && user.id == self.user_id
if user && user.id == self.user_id
if self.is_moderated?
return false
else
Expand All @@ -262,8 +275,18 @@ def is_editable_by_user?(user)
end
end

def is_deletable_by_user?(user)
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id
return true
else
return false
end
end

def is_undeletable_by_user?(user)
if user && user.is_admin?
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id && !self.is_moderated?
return true
Expand Down
10 changes: 10 additions & 0 deletions app/models/moderation.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Moderation < ActiveRecord::Base
belongs_to :moderator,
:class_name => "User",
:foreign_key => "moderator_user_id"
belongs_to :story
belongs_to :comment
belongs_to :user

attr_accessible nil
end
54 changes: 43 additions & 11 deletions app/models/story.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ class Story < ActiveRecord::Base
attr_accessor :_comment_count
attr_accessor :vote, :already_posted_story, :fetched_content, :previewing
attr_accessor :new_tags, :tags_to_add, :tags_to_delete
attr_accessor :editor_user_id, :moderation_reason

attr_accessible :title, :description, :tags_a
attr_accessible :title, :description, :tags_a, :moderation_reason

before_create :assign_short_id
before_save :log_moderation
after_create :mark_submitter
after_save :deal_with_tags

Expand All @@ -31,7 +33,7 @@ class Story < ActiveRecord::Base
indexes tags(:tag), :as => :tags

has created_at, :sortable => true
has hotness, is_moderated, is_expired
has hotness, is_expired
has "(upvotes - downvotes)", :as => :score, :type => :integer,
:sortable => true

Expand All @@ -40,7 +42,7 @@ class Story < ActiveRecord::Base
:tags => 5,
}

where "is_moderated = 0 AND is_expired = 0"
where "is_expired = 0"
end

validate do
Expand Down Expand Up @@ -118,6 +120,37 @@ def assign_short_id
end
end

def log_moderation
if self.new_record? || self.editor_user_id == self.user_id
return
end

m = Moderation.new
m.moderator_user_id = self.editor_user_id
m.story_id = self.id

if self.changes["is_expired"] && self.is_expired?
m.action = "deleted story"
elsif self.changes["is_expired"] && !self.is_expired?
m.action = "undeleted story"
else
actions = self.changes.map{|k,v| "changed #{k} from #{v[0].inspect} " <<
"to #{v[1].inspect}" }

if (old_tags = self.tags.map{|t| t.tag }) != self.tags_a
actions.push "changed tags from \"#{old_tags.join(", ")}\" to " <<
"\"#{self.tags_a.join(", ")}\""
end

m.action = actions.join(", ")
end

m.reason = self.moderation_reason
m.save

self.is_moderated = true
end

def give_upvote_or_downvote_and_recalculate_hotness!(upvote, downvote)
self.upvotes += upvote.to_i
self.downvotes += downvote.to_i
Expand Down Expand Up @@ -236,7 +269,7 @@ def tags_a
def tags_a=(new_tags)
self.tags_to_delete = []
self.tags_to_add = []
self.new_tags = new_tags
self.new_tags = new_tags.reject{|t| t.blank? }

self.tags.each do |tag|
if !new_tags.include?(tag.tag)
Expand All @@ -252,7 +285,7 @@ def tags_a=(new_tags)
end
end

@_tags_a = new_tags
@_tags_a = self.new_tags
end

def url=(u)
Expand Down Expand Up @@ -286,7 +319,7 @@ def url_or_comments_url
end

def is_editable_by_user?(user)
if user && user.is_admin?
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id
if self.is_moderated?
Expand All @@ -300,7 +333,7 @@ def is_editable_by_user?(user)
end

def is_undeletable_by_user?(user)
if user && user.is_admin?
if user && user.is_moderator?
return true
elsif user && user.id == self.user_id && !self.is_moderated?
return true
Expand All @@ -310,20 +343,19 @@ def is_undeletable_by_user?(user)
end

def can_be_seen_by_user?(user)
if is_gone? && !(user && (user.is_admin? || user.id == self.user_id))
if is_gone? && !(user && (user.is_moderator? || user.id == self.user_id))
return false
end

true
end

def is_gone?
is_expired? || is_moderated?
is_expired?
end

def update_comment_count!
Keystore.put("story:#{self.id}:comment_count",
Comment.where(:story_id => self.id, :is_moderated => 0,
:is_deleted => 0).count)
Comment.where(:story_id => self.id, :is_deleted => 0).count)
end
end
6 changes: 3 additions & 3 deletions app/views/comments/_comment.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% if comment.is_gone? && comment.is_undeletable_by_user?(@user) %>
|
<a class="comment_undeletor">undelete</a>
<% elsif !comment.is_gone? && comment.is_editable_by_user?(@user) %>
<% elsif !comment.is_gone? && comment.is_deletable_by_user?(@user) %>
|
<a class="comment_deletor">delete</a>
<% end %>
Expand All @@ -73,8 +73,8 @@ class="comment <%= comment.current_vote ? (comment.current_vote[:vote] == 1 ?
<% if comment.is_gone? %>
<p>
<span class="na">
[Comment removed by <%= comment.is_deleted? ? "author" :
"moderator" %>]
[Comment removed by <%= comment.is_moderated? ? "moderator" :
"author" %>]
</span>
</p>
<% else %>
Expand Down
4 changes: 2 additions & 2 deletions app/views/home/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
<% end %>
<a href="<%= @tag ? "/t/#{@tag.tag}" : (@newest ? "/newest" : "")
%>/page/<%= @page + 1 %>">Page <%= @page + 1 %> &gt;&gt;</a>
</div>
<% end %>
<% end %>
</div>
Loading

0 comments on commit e6c74e8

Please sign in to comment.