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