mirror of
https://github.com/discourse/discourse.git
synced 2024-12-18 17:14:01 +08:00
fbcc35b417
We have not used anything related to bookmarks for PostAction or UserAction records since 2020, bookmarks are their own thing now. Deleting all this is just cleaning up old cruft.
275 lines
9.3 KiB
Ruby
275 lines
9.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class PostAction < ActiveRecord::Base
|
|
include RateLimiter::OnCreateRecord
|
|
include Trashable
|
|
|
|
belongs_to :post
|
|
belongs_to :user
|
|
belongs_to :post_action_type
|
|
belongs_to :related_post, class_name: 'Post'
|
|
belongs_to :target_user, class_name: 'User'
|
|
|
|
rate_limit :post_action_rate_limiter
|
|
|
|
scope :spam_flags, -> { where(post_action_type_id: PostActionType.types[:spam]) }
|
|
scope :flags, -> { where(post_action_type_id: PostActionType.notify_flag_type_ids) }
|
|
scope :publics, -> { where(post_action_type_id: PostActionType.public_type_ids) }
|
|
scope :active, -> { where(disagreed_at: nil, deferred_at: nil, agreed_at: nil, deleted_at: nil) }
|
|
|
|
after_save :update_counters
|
|
validate :ensure_unique_actions, on: :create
|
|
|
|
def self.counts_for(collection, user)
|
|
return {} if collection.blank? || !user
|
|
|
|
collection_ids = collection.map(&:id)
|
|
user_id = user.try(:id) || 0
|
|
|
|
post_actions = PostAction.where(post_id: collection_ids, user_id: user_id)
|
|
|
|
user_actions = {}
|
|
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
|
|
end
|
|
|
|
user_actions
|
|
end
|
|
|
|
def self.lookup_for(user, topics, post_action_type_id)
|
|
return if topics.blank?
|
|
# in critical path 2x faster than AR
|
|
#
|
|
topic_ids = topics.map(&:id)
|
|
map = {}
|
|
|
|
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|
|
|
(map[row.topic_id] ||= []) << row.post_number
|
|
end
|
|
|
|
map
|
|
end
|
|
|
|
def self.count_per_day_for_type(post_action_type, opts = nil)
|
|
opts ||= {}
|
|
result = unscoped.where(post_action_type_id: post_action_type)
|
|
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]
|
|
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
|
|
result.group('date(post_actions.created_at)')
|
|
.order('date(post_actions.created_at)')
|
|
.count
|
|
end
|
|
|
|
def add_moderator_post_if_needed(moderator, disposition, delete_post = false)
|
|
return if !SiteSetting.auto_respond_to_flag_actions
|
|
return if related_post.nil? || related_post.topic.nil?
|
|
return if staff_already_replied?(related_post.topic)
|
|
message_key = +"flags_dispositions.#{disposition}"
|
|
message_key << "_and_deleted" if delete_post
|
|
|
|
I18n.with_locale(SiteSetting.default_locale) do
|
|
related_post.topic.add_moderator_post(moderator, I18n.t(message_key))
|
|
end
|
|
|
|
# archive message for moderators
|
|
GroupArchivedMessage.archive!(Group[:moderators].id, related_post.topic)
|
|
end
|
|
|
|
def staff_already_replied?(topic)
|
|
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?
|
|
end
|
|
|
|
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
|
|
|
|
def self.act(created_by, post, post_action_type_id, opts = {})
|
|
Discourse.deprecate(
|
|
"PostAction.act is deprecated. Use `PostActionCreator` instead.",
|
|
output_in_test: true,
|
|
drop_from: '2.9.0',
|
|
)
|
|
|
|
result = PostActionCreator.new(
|
|
created_by,
|
|
post,
|
|
post_action_type_id,
|
|
message: opts[:message]
|
|
).perform
|
|
|
|
result.success? ? result.post_action : nil
|
|
end
|
|
|
|
def self.copy(original_post, target_post)
|
|
cols_to_copy = (column_names - %w{id post_id}).join(', ')
|
|
|
|
DB.exec <<~SQL
|
|
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
|
|
|
|
def self.remove_act(user, post, post_action_type_id)
|
|
Discourse.deprecate(
|
|
"PostAction.remove_act is deprecated. Use `PostActionDestroyer` instead.",
|
|
output_in_test: true,
|
|
drop_from: '2.9.0',
|
|
)
|
|
|
|
PostActionDestroyer.new(user, post, post_action_type_id).perform
|
|
end
|
|
|
|
def remove_act!(user)
|
|
trash!(user)
|
|
# NOTE: save is called to ensure all callbacks are called
|
|
# trash will not trigger callbacks, and triggering after_commit
|
|
# is not trivial
|
|
save
|
|
end
|
|
|
|
def is_like?
|
|
post_action_type_id == PostActionType.types[:like]
|
|
end
|
|
|
|
def is_flag?
|
|
!!PostActionType.notify_flag_types[post_action_type_id]
|
|
end
|
|
|
|
def is_private_message?
|
|
post_action_type_id == PostActionType.types[:notify_user] ||
|
|
post_action_type_id == PostActionType.types[:notify_moderators]
|
|
end
|
|
|
|
# A custom rate limiter for this model
|
|
def post_action_rate_limiter
|
|
return unless is_flag? || is_like?
|
|
|
|
return @rate_limiter if @rate_limiter.present?
|
|
|
|
%w(like flag).each do |type|
|
|
if public_send("is_#{type}?")
|
|
limit = SiteSetting.get("max_#{type}s_per_day")
|
|
|
|
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
|
|
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)
|
|
return @rate_limiter
|
|
end
|
|
end
|
|
end
|
|
|
|
def ensure_unique_actions
|
|
post_action_type_ids = is_flag? ? PostActionType.notify_flag_types.values : post_action_type_id
|
|
|
|
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?
|
|
|
|
errors.add(:post_action_type_id) if acted
|
|
end
|
|
|
|
def post_action_type_key
|
|
PostActionType.types[post_action_type_id]
|
|
end
|
|
|
|
def update_counters
|
|
# Update denormalized counts
|
|
column = "#{post_action_type_key}_count"
|
|
count = PostAction.where(post_id: post_id)
|
|
.where(post_action_type_id: post_action_type_id)
|
|
.count
|
|
|
|
# We probably want to refactor this method to something cleaner.
|
|
case post_action_type_key
|
|
when :like
|
|
# 'like_score' is weighted higher for staff accounts
|
|
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]
|
|
else
|
|
if ActiveRecord::Base.connection.column_exists?(:posts, column)
|
|
Post.where(id: post_id).update_all ["#{column} = ?", count]
|
|
end
|
|
end
|
|
|
|
topic_id = Post.with_deleted.where(id: post_id).pluck_first(:topic_id)
|
|
|
|
# topic_user
|
|
if post_action_type_key == :like
|
|
TopicUser.update_post_action_cache(user_id: user_id,
|
|
topic_id: topic_id,
|
|
post_action_type: post_action_type_key)
|
|
end
|
|
|
|
if column == "like_count"
|
|
Topic.find_by(id: topic_id)&.update_action_counts
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
# == 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
|
|
# created_at :datetime not null
|
|
# updated_at :datetime not null
|
|
# deleted_by_id :integer
|
|
# related_post_id :integer
|
|
# staff_took_action :boolean default(FALSE), not null
|
|
# deferred_by_id :integer
|
|
# targets_topic :boolean default(FALSE), not null
|
|
# agreed_at :datetime
|
|
# agreed_by_id :integer
|
|
# deferred_at :datetime
|
|
# disagreed_at :datetime
|
|
# disagreed_by_id :integer
|
|
#
|
|
# Indexes
|
|
#
|
|
# 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)
|
|
# index_post_actions_on_user_id (user_id)
|
|
# index_post_actions_on_user_id_and_post_action_type_id (user_id,post_action_type_id) WHERE (deleted_at IS NULL)
|
|
#
|