2019-05-03 06:17:27 +08:00
# frozen_string_literal: true
2013-02-06 03:16:51 +08:00
class PostAction < ActiveRecord :: Base
include RateLimiter :: OnCreateRecord
2013-05-07 12:39:01 +08:00
include Trashable
2013-02-06 03:16:51 +08:00
belongs_to :post
belongs_to :user
belongs_to :post_action_type
2013-05-30 04:49:34 +08:00
belongs_to :related_post , class_name : 'Post'
2014-02-06 06:54:16 +08:00
belongs_to :target_user , class_name : 'User'
2013-02-06 03:16:51 +08:00
rate_limit :post_action_rate_limiter
2013-05-31 23:41:40 +08:00
scope :spam_flags , - > { where ( post_action_type_id : PostActionType . types [ :spam ] ) }
2014-07-29 01:17:37 +08: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 17:56:54 +08:00
scope :active , - > { where ( disagreed_at : nil , deferred_at : nil , agreed_at : nil , deleted_at : nil ) }
2013-05-31 23:41:40 +08:00
2013-08-15 11:44:30 +08:00
after_save :update_counters
2019-01-04 01:03:01 +08:00
validate :ensure_unique_actions , on : :create
2013-02-06 03:16:51 +08:00
def self . counts_for ( collection , user )
2015-09-28 14:42:05 +08:00
return { } if collection . blank? || ! user
2013-02-06 03:16:51 +08:00
2014-07-29 01:17:37 +08:00
collection_ids = collection . map ( & :id )
2014-07-31 05:35:42 +08:00
user_id = user . try ( :id ) || 0
2013-02-06 03:16:51 +08:00
2014-07-29 01:17:37 +08:00
post_actions = PostAction . where ( post_id : collection_ids , user_id : user_id )
2014-06-04 23:41:11 +08:00
2013-02-06 03:16:51 +08:00
user_actions = { }
2014-07-29 01:17:37 +08: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-06 03:16:51 +08:00
end
2013-02-07 23:45:24 +08:00
2013-02-06 03:16:51 +08:00
user_actions
2013-02-07 23:45:24 +08:00
end
2013-02-06 03:16:51 +08:00
2015-01-07 15:20:10 +08:00
def self . lookup_for ( user , topics , post_action_type_id )
return if topics . blank?
2015-04-16 15:29:18 +08:00
# in critical path 2x faster than AR
#
topic_ids = topics . map ( & :id )
2015-01-07 15:20:10 +08:00
map = { }
2018-06-20 15:48:02 +08: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 15:29:18 +08:00
( map [ row . topic_id ] || = [ ] ) << row . post_number
2015-01-07 15:20:10 +08:00
end
map
end
2015-06-24 21:19:39 +08:00
def self . count_per_day_for_type ( post_action_type , opts = nil )
opts || = { }
result = unscoped . where ( post_action_type_id : post_action_type )
2015-10-20 04:30:34 +08: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 ]
2020-04-22 16:52:50 +08:00
if opts [ :category_id ]
if opts [ :include_subcategories ]
result = result . joins ( post : :topic ) . where ( 'topics.category_id IN (?)' , Category . subcategory_ids ( opts [ :category_id ] ) )
else
result = result . joins ( post : :topic ) . where ( 'topics.category_id = ?' , opts [ :category_id ] )
end
end
2015-06-24 21:19:39 +08:00
result . group ( 'date(post_actions.created_at)' )
. order ( 'date(post_actions.created_at)' )
. count
2014-07-29 01:17:37 +08:00
end
def add_moderator_post_if_needed ( moderator , disposition , delete_post = false )
2015-03-12 02:29:09 +08:00
return if ! SiteSetting . auto_respond_to_flag_actions
2015-03-04 00:52:46 +08:00
return if related_post . nil? || related_post . topic . nil?
2015-03-16 19:02:34 +08:00
return if staff_already_replied? ( related_post . topic )
2019-05-03 06:17:27 +08:00
message_key = + " flags_dispositions. #{ disposition } "
2014-07-29 01:17:37 +08:00
message_key << " _and_deleted " if delete_post
2017-08-09 17:17:54 +08:00
I18n . with_locale ( SiteSetting . default_locale ) do
related_post . topic . add_moderator_post ( moderator , I18n . t ( message_key ) )
end
2021-04-22 18:23:44 +08:00
# archive message for moderators
GroupArchivedMessage . archive! ( Group [ :moderators ] . id , related_post . topic )
2014-07-29 01:17:37 +08:00
end
2015-03-16 19:02:34 +08:00
def staff_already_replied? ( topic )
2015-07-25 04:39:03 +08: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 23:00:14 +08:00
end
2016-03-06 06:51:30 +08:00
def self . limit_action! ( user , post , post_action_type_id )
RateLimiter . new ( user , " post_action- #{ post . id } _ #{ post_action_type_id } " , 4 , 1 . minute ) . performed!
end
2019-01-04 01:03:01 +08:00
def self . act ( created_by , post , post_action_type_id , opts = { } )
2021-11-12 22:52:59 +08:00
Discourse . deprecate (
" PostAction.act is deprecated. Use `PostActionCreator` instead. " ,
output_in_test : true ,
drop_from : '2.9.0' ,
)
2016-03-06 06:51:30 +08:00
2019-01-04 01:03:01 +08:00
result = PostActionCreator . new (
created_by ,
post ,
post_action_type_id ,
message : opts [ :message ]
) . perform
2018-06-20 21:19:37 +08:00
2019-01-04 01:03:01 +08:00
result . success? ? result . post_action : nil
2019-02-08 02:46:05 +08:00
end
2017-02-10 22:35:04 +08:00
def self . copy ( original_post , target_post )
cols_to_copy = ( column_names - %w{ id post_id } ) . join ( ', ' )
2018-06-19 14:13:14 +08:00
DB . exec << ~ SQL
2017-02-10 22:35:04 +08: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-06 03:16:51 +08:00
def self . remove_act ( user , post , post_action_type_id )
2019-01-04 01:03:01 +08:00
Discourse . deprecate (
" PostAction.remove_act is deprecated. Use `PostActionDestroyer` instead. " ,
2021-11-12 22:52:59 +08:00
output_in_test : true ,
drop_from : '2.9.0' ,
2019-01-04 01:03:01 +08:00
)
2016-03-06 06:51:30 +08:00
2019-01-04 01:03:01 +08:00
PostActionDestroyer . new ( user , post , post_action_type_id ) . perform
2013-02-06 03:16:51 +08:00
end
2013-05-13 09:48:01 +08:00
def remove_act! ( user )
2013-07-10 03:20:18 +08:00
trash! ( user )
2014-03-31 09:34:01 +08: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-13 09:48:01 +08:00
end
2013-02-07 23:45:24 +08:00
def is_like?
2013-03-01 20:07:44 +08:00
post_action_type_id == PostActionType . types [ :like ]
2013-02-06 03:16:51 +08:00
end
def is_flag?
2018-02-28 11:22:51 +08:00
! ! PostActionType . notify_flag_types [ post_action_type_id ]
2013-02-07 23:45:24 +08:00
end
2013-02-06 03:16:51 +08:00
2013-04-12 15:55:45 +08:00
def is_private_message?
post_action_type_id == PostActionType . types [ :notify_user ] ||
post_action_type_id == PostActionType . types [ :notify_moderators ]
end
2013-05-11 04:58:23 +08:00
2013-02-06 03:16:51 +08:00
# A custom rate limiter for this model
def post_action_rate_limiter
2022-05-10 08:42:18 +08:00
return unless is_flag? || is_like?
2013-02-06 03:16:51 +08:00
return @rate_limiter if @rate_limiter . present?
2022-05-10 08:42:18 +08:00
%w( like flag ) . each do | type |
2019-05-07 09:57:55 +08:00
if public_send ( " is_ #{ type } ? " )
2019-05-07 09:00:09 +08:00
limit = SiteSetting . get ( " max_ #{ type } s_per_day " )
2015-04-16 07:44:30 +08:00
2022-02-18 20:44:32 +08:00
if ( is_flag? || is_like? ) && user && user . trust_level > = 2
multiplier = SiteSetting . get ( " tl #{ user . trust_level } _additional_ #{ type } s_per_day_multiplier " ) . to_f
2015-04-16 07:44:30 +08:00
multiplier = 1 . 0 if multiplier < 1 . 0
limit = ( limit * multiplier ) . to_i
end
@rate_limiter = RateLimiter . new ( user , " create_ #{ type } " , limit , 1 . day . to_i )
2013-02-06 03:16:51 +08:00
return @rate_limiter
end
end
end
2013-02-09 05:55:40 +08:00
2019-01-04 01:03:01 +08:00
def ensure_unique_actions
2018-02-28 11:22:51 +08:00
post_action_type_ids = is_flag? ? PostActionType . notify_flag_types . values : post_action_type_id
2019-01-04 01:03:01 +08:00
acted = PostAction . where ( user_id : user_id )
2018-12-04 17:48:16 +08: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-07 07:45:58 +08:00
2019-01-04 01:03:01 +08:00
errors . add ( :post_action_type_id ) if acted
2013-05-11 04:58:23 +08:00
end
2013-08-15 11:44:30 +08:00
def post_action_type_key
PostActionType . types [ post_action_type_id ]
end
def update_counters
2013-02-06 03:16:51 +08:00
# Update denormalized counts
2014-08-15 02:20:52 +08:00
column = " #{ post_action_type_key } _count "
2014-07-29 01:17:37 +08:00
count = PostAction . where ( post_id : post_id )
. where ( post_action_type_id : post_action_type_id )
. count
2013-02-06 03:16:51 +08:00
2013-05-28 00:45:10 +08:00
# We probably want to refactor this method to something cleaner.
2013-08-15 11:44:30 +08:00
case post_action_type_key
2013-05-28 00:45:10 +08:00
when :like
2014-12-06 02:37:43 +08:00
# 'like_score' is weighted higher for staff accounts
2014-07-29 01:17:37 +08:00
score = PostAction . joins ( :user )
. where ( post_id : post_id )
. sum ( " CASE WHEN users.moderator OR users.admin THEN #{ SiteSetting . staff_like_weight } ELSE 1 END " )
Post . where ( id : post_id ) . update_all [ " like_count = :count, like_score = :score " , count : count , score : score ]
2013-02-06 03:16:51 +08:00
else
2017-10-18 01:31:45 +08:00
if ActiveRecord :: Base . connection . column_exists? ( :posts , column )
Post . where ( id : post_id ) . update_all [ " #{ column } = ? " , count ]
end
2013-02-06 03:16:51 +08:00
end
2013-05-28 00:45:10 +08:00
2019-10-21 18:32:27 +08:00
topic_id = Post . with_deleted . where ( id : post_id ) . pluck_first ( :topic_id )
2015-01-08 11:35:56 +08:00
# topic_user
2020-07-08 13:27:42 +08:00
if post_action_type_key == :like
2015-01-08 11:35:56 +08: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 14:03:31 +08:00
if column == " like_count "
2022-02-03 13:24:33 +08:00
Topic . find_by ( id : topic_id ) & . update_action_counts
2016-12-02 14:03:31 +08:00
end
2013-02-06 03:16:51 +08:00
2013-06-20 15:42:15 +08:00
end
2013-02-06 03:16:51 +08:00
end
2013-05-24 10:48:32 +08: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 13:19:25 +08:00
# created_at :datetime not null
# updated_at :datetime not null
2013-07-10 03:20:18 +08:00
# deleted_by_id :integer
2013-05-24 10:48:32 +08:00
# related_post_id :integer
2013-06-17 08:48:58 +08:00
# staff_took_action :boolean default(FALSE), not null
2014-08-23 01:01:44 +08:00
# deferred_by_id :integer
# targets_topic :boolean default(FALSE), not null
2014-07-29 01:17:37 +08:00
# agreed_at :datetime
# agreed_by_id :integer
2014-08-23 01:01:44 +08:00
# deferred_at :datetime
2014-07-31 11:14:40 +08:00
# disagreed_at :datetime
# disagreed_by_id :integer
2013-05-24 10:48:32 +08:00
#
# Indexes
#
2019-04-02 13:17:55 +08: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 16:23:27 +08:00
# index_post_actions_on_user_id (user_id)
2019-04-02 13:17:55 +08: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-24 10:48:32 +08:00
#