discourse/app/models/post_action.rb
2013-03-18 17:54:43 -04:00

178 lines
6.2 KiB
Ruby

require_dependency 'rate_limiter'
require_dependency 'system_message'
class PostAction < ActiveRecord::Base
class AlreadyFlagged < StandardError; end
include RateLimiter::OnCreateRecord
attr_accessible :post_action_type_id, :post_id, :user_id, :post, :user, :post_action_type, :message
belongs_to :post
belongs_to :user
belongs_to :post_action_type
acts_as_paranoid
rate_limit :post_action_rate_limiter
validate :message_quality
def self.update_flagged_posts_count
posts_flagged_count = PostAction.joins(post: :topic)
.where('post_actions.post_action_type_id' => PostActionType.flag_types.values,
'posts.deleted_at' => nil,
'topics.deleted_at' => nil).count('DISTINCT posts.id')
$redis.set('posts_flagged_count', posts_flagged_count)
admins = User.where(admin: true).select(:id).map {|u| u.id}
MessageBus.publish('/flagged_counts', { total: posts_flagged_count }, { user_ids: admins })
end
def self.flagged_posts_count
$redis.get('posts_flagged_count').to_i
end
def self.counts_for(collection, user)
return {} if collection.blank?
collection_ids = collection.map {|p| p.id}
user_id = user.present? ? user.id : 0
result = PostAction.where(post_id: collection_ids, user_id: user_id)
user_actions = {}
result.each do |r|
user_actions[r.post_id] ||= {}
user_actions[r.post_id][r.post_action_type_id] = r
end
user_actions
end
def self.count_likes_per_day(since = 30.days.ago)
where(post_action_type_id: PostActionType.types[:like]).where('created_at > ?', since).group('date(created_at)').order('date(created_at)').count
end
def self.clear_flags!(post, moderator_id, action_type_id = nil)
# -1 is the automatic system cleary
actions = if action_type_id
[action_type_id]
else
moderator_id == -1 ? PostActionType.auto_action_flag_types.values : PostActionType.flag_types.values
end
PostAction.update_all({ deleted_at: Time.now, deleted_by: moderator_id }, { post_id: post.id, post_action_type_id: actions })
f = actions.map{|t| ["#{PostActionType.types[t]}_count", 0]}
Post.with_deleted.update_all(Hash[*f.flatten], id: post.id)
update_flagged_posts_count
end
def self.act(user, post, post_action_type_id, message = nil)
begin
create(post_id: post.id, user_id: user.id, post_action_type_id: post_action_type_id, message: message)
rescue ActiveRecord::RecordNotUnique
# can happen despite being .create
# since already bookmarked
true
end
end
def self.remove_act(user, post, post_action_type_id)
if action = where(post_id: post.id, user_id: user.id, post_action_type_id: post_action_type_id).first
action.destroy
action.deleted_at = Time.now
action.run_callbacks(:save)
end
end
def is_bookmark?
post_action_type_id == PostActionType.types[:bookmark]
end
def is_like?
post_action_type_id == PostActionType.types[:like]
end
def is_flag?
PostActionType.flag_types.values.include?(post_action_type_id)
end
# A custom rate limiter for this model
def post_action_rate_limiter
return unless is_flag? || is_bookmark? || is_like?
return @rate_limiter if @rate_limiter.present?
%w(like flag bookmark).each do |type|
if send("is_#{type}?")
@rate_limiter = RateLimiter.new(user, "create_#{type}:#{Date.today.to_s}", SiteSetting.send("max_#{type}s_per_day"), 1.day.to_i)
return @rate_limiter
end
end
end
def message_quality
return if message.blank?
sentinel = TextSentinel.title_sentinel(message)
if sentinel.valid?
# It's possible the sentinel has cleaned up the title a bit
self.message = sentinel.text
else
errors.add(:message, I18n.t(:is_invalid)) unless sentinel.valid?
end
end
before_create do
raise AlreadyFlagged if is_flag? && PostAction.where(user_id: user_id,
post_id: post_id,
post_action_type_id: PostActionType.flag_types.values).exists?
end
after_save do
# Update denormalized counts
post_action_type = PostActionType.types[post_action_type_id]
column = "#{post_action_type.to_s}_count"
delta = deleted_at.nil? ? 1 : -1
# Voting also changes the sort_order
if post_action_type == :vote
Post.update_all ["vote_count = vote_count + :delta, sort_order = :max - (vote_count + :delta)", delta: delta, max: Topic.max_sort_order], id: post_id
else
Post.update_all ["#{column} = #{column} + ?", delta], id: post_id
end
Topic.update_all ["#{column} = #{column} + ?", delta], id: post.topic_id
if PostActionType.flag_types.values.include?(post_action_type_id)
PostAction.update_flagged_posts_count
end
if SiteSetting.flags_required_to_hide_post > 0
# automatic hiding of posts
flag_counts = exec_sql("SELECT SUM(CASE WHEN deleted_at IS NULL THEN 1 ELSE 0 END) AS new_flags,
SUM(CASE WHEN deleted_at IS NOT NULL THEN 1 ELSE 0 END) AS old_flags
FROM post_actions
WHERE post_id = ? AND post_action_type_id IN (?)", post.id, PostActionType.auto_action_flag_types.values).first
old_flags, new_flags = flag_counts['old_flags'].to_i, flag_counts['new_flags'].to_i
if new_flags >= SiteSetting.flags_required_to_hide_post
reason = old_flags > 0 ? Post.hidden_reasons[:flag_threshold_reached_again] : Post.hidden_reasons[:flag_threshold_reached]
Post.update_all(["hidden = true, hidden_reason_id = COALESCE(hidden_reason_id, ?)", reason], id: post_id)
Topic.update_all({ visible: false },
["id = :topic_id AND NOT EXISTS(SELECT 1 FROM POSTS WHERE topic_id = :topic_id AND NOT hidden)", topic_id: post.topic_id])
# inform user
if post.user
SystemMessage.create(post.user, :post_hidden,
url: post.url,
edit_delay: SiteSetting.cooldown_minutes_after_hiding_posts)
end
end
end
end
end