2019-05-02 18:17:27 -04:00
# frozen_string_literal: true
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 )
2015-04-16 03:29:18 -04:00
. each { | row | ( map [ row . topic_id ] || = [ ] ) << row . post_number }
2015-01-07 02:20:10 -05:00
map
end
2015-06-24 09:19:39 -04: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-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 ]
2020-04-22 04:52:50 -04: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 09:19:39 -04:00
result . group ( " date(post_actions.created_at) " ) . order ( " date(post_actions.created_at) " ) . count
2014-07-28 13:17:37 -04:00
end
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 )
2019-05-02 18:17:27 -04:00
message_key = + " flags_dispositions. #{ disposition } "
2014-07-28 13:17:37 -04:00
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
2021-04-22 06:23:44 -04:00
# archive message for moderators
GroupArchivedMessage . archive! ( Group [ :moderators ] . id , related_post . topic )
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 ] ,
2023-01-09 07:20:10 -05:00
)
2015-07-24 16:39:03 -04:00
. exists?
2014-08-18 11:00:14 -04:00
end
2016-03-05 17:51:30 -05: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-03 12:03:01 -05:00
def self . act ( created_by , post , post_action_type_id , opts = { } )
2021-11-12 09:52:59 -05:00
Discourse . deprecate (
" PostAction.act is deprecated. Use `PostActionCreator` instead. " ,
output_in_test : true ,
drop_from : " 2.9.0 " ,
)
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. " ,
2021-11-12 09:52:59 -05:00
output_in_test : true ,
drop_from : " 2.9.0 " ,
2019-01-03 12:03:01 -05:00
)
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_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
2022-05-09 20:42:18 -04:00
return unless is_flag? || is_like?
2013-02-05 14:16:51 -05:00
return @rate_limiter if @rate_limiter . present?
2022-05-09 20:42:18 -04:00
%w[ like flag ] . each do | type |
2019-05-06 21:57:55 -04:00
if public_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
2022-02-18 07:44:32 -05: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-15 19:44:30 -04: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-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 ) . 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 )
. 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-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
2019-10-21 06:32:27 -04:00
topic_id = Post . with_deleted . where ( id : post_id ) . pluck_first ( :topic_id )
2015-01-07 22:35:56 -05:00
# topic_user
2020-07-08 01:27:42 -04:00
if post_action_type_key == :like
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 ,
)
2016-12-02 01:03:31 -05:00
end
2013-02-05 14:16:51 -05:00
2022-02-03 00:24:33 -05:00
Topic . find_by ( id : topic_id ) & . update_action_counts if column == " like_count "
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
#