2013-02-05 14:16:51 -05:00
require_dependency 'rate_limiter'
require_dependency 'system_message'
2019-01-03 12:03:01 -05:00
require_dependency 'post_action_creator'
require_dependency 'post_action_destroyer'
2013-02-05 14:16:51 -05:00
class PostAction < ActiveRecord :: Base
include RateLimiter :: OnCreateRecord
2013-05-07 00:39:01 -04:00
include Trashable
2013-02-05 14:16:51 -05:00
belongs_to :post
belongs_to :user
belongs_to :post_action_type
2013-05-29 16:49:34 -04:00
belongs_to :related_post , class_name : 'Post'
2014-02-05 17:54:16 -05:00
belongs_to :target_user , class_name : 'User'
2013-02-05 14:16:51 -05:00
rate_limit :post_action_rate_limiter
2013-05-31 11:41:40 -04:00
scope :spam_flags , - > { where ( post_action_type_id : PostActionType . types [ :spam ] ) }
2014-07-28 13:17:37 -04:00
scope :flags , - > { where ( post_action_type_id : PostActionType . notify_flag_type_ids ) }
scope :publics , - > { where ( post_action_type_id : PostActionType . public_type_ids ) }
2014-08-11 05:56:54 -04:00
scope :active , - > { where ( disagreed_at : nil , deferred_at : nil , agreed_at : nil , deleted_at : nil ) }
2013-05-31 11:41:40 -04:00
2013-08-14 23:44:30 -04:00
after_save :update_counters
2019-01-03 12:03:01 -05:00
validate :ensure_unique_actions , on : :create
2013-02-05 14:16:51 -05:00
def self . counts_for ( collection , user )
2015-09-28 02:42:05 -04:00
return { } if collection . blank? || ! user
2013-02-05 14:16:51 -05:00
2014-07-28 13:17:37 -04:00
collection_ids = collection . map ( & :id )
2014-07-30 17:35:42 -04:00
user_id = user . try ( :id ) || 0
2013-02-05 14:16:51 -05:00
2014-07-28 13:17:37 -04:00
post_actions = PostAction . where ( post_id : collection_ids , user_id : user_id )
2014-06-04 11:41:11 -04:00
2013-02-05 14:16:51 -05:00
user_actions = { }
2014-07-28 13:17:37 -04:00
post_actions . each do | post_action |
user_actions [ post_action . post_id ] || = { }
user_actions [ post_action . post_id ] [ post_action . post_action_type_id ] = post_action
2013-02-05 14:16:51 -05:00
end
2013-02-07 10:45:24 -05:00
2013-02-05 14:16:51 -05:00
user_actions
2013-02-07 10:45:24 -05:00
end
2013-02-05 14:16:51 -05:00
2015-01-07 02:20:10 -05:00
def self . lookup_for ( user , topics , post_action_type_id )
return if topics . blank?
2015-04-16 03:29:18 -04:00
# in critical path 2x faster than AR
#
topic_ids = topics . map ( & :id )
2015-01-07 02:20:10 -05:00
map = { }
2018-06-20 03:48:02 -04:00
builder = DB . build << ~ SQL
SELECT p . topic_id , p . post_number
FROM post_actions pa
JOIN posts p ON pa . post_id = p . id
WHERE p . deleted_at IS NULL AND pa . deleted_at IS NULL AND
pa . post_action_type_id = :post_action_type_id AND
pa . user_id = :user_id AND
p . topic_id IN ( :topic_ids )
ORDER BY p . topic_id , p . post_number
SQL
builder . query ( user_id : user . id , post_action_type_id : post_action_type_id , topic_ids : topic_ids ) . each do | row |
2015-04-16 03:29:18 -04:00
( map [ row . topic_id ] || = [ ] ) << row . post_number
2015-01-07 02:20:10 -05:00
end
map
end
2017-07-27 21:20:09 -04:00
def self . count_per_day_for_type ( post_action_type , opts = nil )
2015-06-24 09:19:39 -04:00
opts || = { }
result = unscoped . where ( post_action_type_id : post_action_type )
2015-10-19 16:30:34 -04:00
result = result . where ( 'post_actions.created_at >= ?' , opts [ :start_date ] || ( opts [ :since_days_ago ] || 30 ) . days . ago )
result = result . where ( 'post_actions.created_at <= ?' , opts [ :end_date ] ) if opts [ :end_date ]
2018-10-02 05:27:43 -04:00
result = result . joins ( post : :topic ) . merge ( Topic . in_category_and_subcategories ( opts [ :category_id ] ) ) if opts [ :category_id ]
2015-06-24 09:19:39 -04:00
result . group ( 'date(post_actions.created_at)' )
2017-07-27 21:20:09 -04:00
. order ( 'date(post_actions.created_at)' )
. count
2014-07-28 13:17:37 -04:00
end
2017-07-27 21:20:09 -04:00
def add_moderator_post_if_needed ( moderator , disposition , delete_post = false )
2015-03-11 14:29:09 -04:00
return if ! SiteSetting . auto_respond_to_flag_actions
2015-03-03 11:52:46 -05:00
return if related_post . nil? || related_post . topic . nil?
2015-03-16 07:02:34 -04:00
return if staff_already_replied? ( related_post . topic )
2014-07-28 13:17:37 -04:00
message_key = " flags_dispositions. #{ disposition } "
message_key << " _and_deleted " if delete_post
2017-08-09 05:17:54 -04:00
I18n . with_locale ( SiteSetting . default_locale ) do
related_post . topic . add_moderator_post ( moderator , I18n . t ( message_key ) )
end
2014-07-28 13:17:37 -04:00
end
2015-03-16 07:02:34 -04:00
def staff_already_replied? ( topic )
2015-07-24 16:39:03 -04:00
topic . posts . where ( " user_id IN (SELECT id FROM users WHERE moderator OR admin) OR (post_type != :regular_post_type) " , regular_post_type : Post . types [ :regular ] ) . exists?
2014-08-18 11:00:14 -04:00
end
2017-07-27 21:20:09 -04:00
def self . limit_action! ( user , post , post_action_type_id )
2016-03-05 17:51:30 -05:00
RateLimiter . new ( user , " post_action- #{ post . id } _ #{ post_action_type_id } " , 4 , 1 . minute ) . performed!
end
2019-01-03 12:03:01 -05:00
def self . act ( created_by , post , post_action_type_id , opts = { } )
Discourse . deprecate ( " PostAction.act is deprecated. Use `PostActionCreator` instead. " , output_in_test : true )
2016-03-05 17:51:30 -05:00
2019-01-03 12:03:01 -05:00
result = PostActionCreator . new (
created_by ,
post ,
post_action_type_id ,
message : opts [ :message ]
) . perform
2018-06-20 09:19:37 -04:00
2019-01-03 12:03:01 -05:00
result . success? ? result . post_action : nil
2019-02-07 13:46:05 -05:00
end
2017-02-10 09:35:04 -05:00
def self . copy ( original_post , target_post )
cols_to_copy = ( column_names - %w{ id post_id } ) . join ( ', ' )
2018-06-19 02:13:14 -04:00
DB . exec << ~ SQL
2017-02-10 09:35:04 -05:00
INSERT INTO post_actions ( post_id , #{cols_to_copy})
SELECT #{target_post.id}, #{cols_to_copy}
FROM post_actions
WHERE post_id = #{original_post.id}
SQL
target_post . post_actions . each { | post_action | post_action . update_counters }
end
2013-02-05 14:16:51 -05:00
def self . remove_act ( user , post , post_action_type_id )
2019-01-03 12:03:01 -05:00
Discourse . deprecate (
" PostAction.remove_act is deprecated. Use `PostActionDestroyer` instead. " ,
output_in_test : true
)
2016-03-05 17:51:30 -05:00
2019-01-03 12:03:01 -05:00
PostActionDestroyer . new ( user , post , post_action_type_id ) . perform
2013-02-05 14:16:51 -05:00
end
2013-05-12 21:48:01 -04:00
def remove_act! ( user )
2013-07-09 15:20:18 -04:00
trash! ( user )
2014-03-30 21:34:01 -04:00
# NOTE: save is called to ensure all callbacks are called
# trash will not trigger callbacks, and triggering after_commit
# is not trivial
save
2013-05-12 21:48:01 -04:00
end
2013-02-07 10:45:24 -05:00
def is_bookmark?
2013-03-01 07:07:44 -05:00
post_action_type_id == PostActionType . types [ :bookmark ]
2013-02-05 14:16:51 -05:00
end
2013-02-07 10:45:24 -05:00
def is_like?
2013-03-01 07:07:44 -05:00
post_action_type_id == PostActionType . types [ :like ]
2013-02-05 14:16:51 -05:00
end
def is_flag?
2018-02-27 22:22:51 -05:00
! ! PostActionType . notify_flag_types [ post_action_type_id ]
2013-02-07 10:45:24 -05:00
end
2013-02-05 14:16:51 -05:00
2013-04-12 03:55:45 -04:00
def is_private_message?
post_action_type_id == PostActionType . types [ :notify_user ] ||
post_action_type_id == PostActionType . types [ :notify_moderators ]
end
2013-05-10 16:58:23 -04:00
2013-02-05 14:16:51 -05:00
# A custom rate limiter for this model
def post_action_rate_limiter
2013-02-28 13:54:12 -05:00
return unless is_flag? || is_bookmark? || is_like?
2013-02-05 14:16:51 -05:00
return @rate_limiter if @rate_limiter . present?
%w( like flag bookmark ) . each do | type |
if send ( " is_ #{ type } ? " )
2019-05-06 21:00:09 -04:00
limit = SiteSetting . get ( " max_ #{ type } s_per_day " )
2015-04-15 19:44:30 -04:00
if is_like? && user && user . trust_level > = 2
2019-05-06 21:00:09 -04:00
multiplier = SiteSetting . get ( " tl #{ user . trust_level } _additional_likes_per_day_multiplier " ) . to_f
2015-04-15 19:44:30 -04:00
multiplier = 1 . 0 if multiplier < 1 . 0
2017-07-27 21:20:09 -04:00
limit = ( limit * multiplier ) . to_i
2015-04-15 19:44:30 -04:00
end
2017-07-27 21:20:09 -04:00
@rate_limiter = RateLimiter . new ( user , " create_ #{ type } " , limit , 1 . day . to_i )
2013-02-05 14:16:51 -05:00
return @rate_limiter
end
end
end
2013-02-08 16:55:40 -05:00
2019-01-03 12:03:01 -05:00
def ensure_unique_actions
2018-02-27 22:22:51 -05:00
post_action_type_ids = is_flag? ? PostActionType . notify_flag_types . values : post_action_type_id
2019-01-03 12:03:01 -05:00
acted = PostAction . where ( user_id : user_id )
2018-12-04 04:48:16 -05:00
. where ( post_id : post_id )
. where ( post_action_type_id : post_action_type_ids )
. where ( deleted_at : nil )
. where ( disagreed_at : nil )
. where ( targets_topic : targets_topic )
. exists?
2013-02-06 18:45:58 -05:00
2019-01-03 12:03:01 -05:00
errors . add ( :post_action_type_id ) if acted
2013-05-10 16:58:23 -04:00
end
2013-08-14 23:44:30 -04:00
def post_action_type_key
PostActionType . types [ post_action_type_id ]
end
def update_counters
2013-02-05 14:16:51 -05:00
# Update denormalized counts
2014-08-14 14:20:52 -04:00
column = " #{ post_action_type_key } _count "
2014-07-28 13:17:37 -04:00
count = PostAction . where ( post_id : post_id )
2017-07-27 21:20:09 -04:00
. where ( post_action_type_id : post_action_type_id )
. count
2013-02-05 14:16:51 -05:00
2013-05-27 12:45:10 -04:00
# We probably want to refactor this method to something cleaner.
2013-08-14 23:44:30 -04:00
case post_action_type_key
2013-05-27 12:45:10 -04:00
when :like
2014-12-05 13:37:43 -05:00
# 'like_score' is weighted higher for staff accounts
2014-07-28 13:17:37 -04:00
score = PostAction . joins ( :user )
2017-07-27 21:20:09 -04:00
. where ( post_id : post_id )
. sum ( " CASE WHEN users.moderator OR users.admin THEN #{ SiteSetting . staff_like_weight } ELSE 1 END " )
2014-07-28 13:17:37 -04:00
Post . where ( id : post_id ) . update_all [ " like_count = :count, like_score = :score " , count : count , score : score ]
2013-02-05 14:16:51 -05:00
else
2017-10-17 13:31:45 -04:00
if ActiveRecord :: Base . connection . column_exists? ( :posts , column )
Post . where ( id : post_id ) . update_all [ " #{ column } = ? " , count ]
end
2013-02-05 14:16:51 -05:00
end
2013-05-27 12:45:10 -04:00
2014-07-28 13:17:37 -04:00
topic_id = Post . with_deleted . where ( id : post_id ) . pluck ( :topic_id ) . first
2015-01-07 22:35:56 -05:00
# topic_user
2017-07-27 21:20:09 -04:00
if [ :like , :bookmark ] . include? post_action_type_key
2015-01-07 22:35:56 -05:00
TopicUser . update_post_action_cache ( user_id : user_id ,
topic_id : topic_id ,
post_action_type : post_action_type_key )
end
2016-12-02 01:03:31 -05:00
if column == " like_count "
topic_count = Post . where ( topic_id : topic_id ) . sum ( column )
Topic . where ( id : topic_id ) . update_all [ " #{ column } = ? " , topic_count ]
end
2013-02-05 14:16:51 -05:00
2013-06-20 03:42:15 -04:00
end
2013-02-05 14:16:51 -05:00
end
2013-05-23 22:48:32 -04:00
# == Schema Information
#
# Table name: post_actions
#
# id :integer not null, primary key
# post_id :integer not null
# user_id :integer not null
# post_action_type_id :integer not null
# deleted_at :datetime
2014-08-27 01:19:25 -04:00
# created_at :datetime not null
# updated_at :datetime not null
2013-07-09 15:20:18 -04:00
# deleted_by_id :integer
2013-05-23 22:48:32 -04:00
# related_post_id :integer
2013-06-16 20:48:58 -04:00
# staff_took_action :boolean default(FALSE), not null
2014-08-22 13:01:44 -04:00
# deferred_by_id :integer
# targets_topic :boolean default(FALSE), not null
2014-07-28 13:17:37 -04:00
# agreed_at :datetime
# agreed_by_id :integer
2014-08-22 13:01:44 -04:00
# deferred_at :datetime
2014-07-30 23:14:40 -04:00
# disagreed_at :datetime
# disagreed_by_id :integer
2013-05-23 22:48:32 -04:00
#
# Indexes
#
2019-04-02 01:17:55 -04:00
# idx_unique_actions (user_id,post_action_type_id,post_id,targets_topic) UNIQUE WHERE ((deleted_at IS NULL) AND (disagreed_at IS NULL) AND (deferred_at IS NULL))
# idx_unique_flags (user_id,post_id,targets_topic) UNIQUE WHERE ((deleted_at IS NULL) AND (disagreed_at IS NULL) AND (deferred_at IS NULL) AND (post_action_type_id = ANY (ARRAY[3, 4, 7, 8])))
# index_post_actions_on_post_action_type_id_and_disagreed_at (post_action_type_id,disagreed_at) WHERE (disagreed_at IS NULL)
# index_post_actions_on_post_id (post_id)
2019-04-26 04:23:27 -04:00
# index_post_actions_on_user_id (user_id)
2019-04-02 01:17:55 -04:00
# index_post_actions_on_user_id_and_post_action_type_id (user_id,post_action_type_id) WHERE (deleted_at IS NULL)
2013-05-23 22:48:32 -04:00
#