mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 20:24:05 -06:00
09932738e5
Before, whispers were only available for staff members. Config has been changed to allow to configure privileged groups with access to whispers. Post migration was added to move from the old setting into the new one. I considered having a boolean column `whisperer` on user model similar to `admin/moderator` for performance reason. Finally, I decided to keep looking for groups as queries are only done for current user and didn't notice any N+1 queries.
230 lines
7.4 KiB
Ruby
230 lines
7.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class PostTiming < ActiveRecord::Base
|
|
belongs_to :topic
|
|
belongs_to :user
|
|
|
|
validates_presence_of :post_number
|
|
validates_presence_of :msecs
|
|
|
|
def self.pretend_read(topic_id, actual_read_post_number, pretend_read_post_number)
|
|
# This is done in SQL cause the logic is quite tricky and we want to do this in one db hit
|
|
#
|
|
DB.exec("INSERT INTO post_timings(topic_id, user_id, post_number, msecs)
|
|
SELECT :topic_id, user_id, :pretend_read_post_number, 1
|
|
FROM post_timings pt
|
|
WHERE topic_id = :topic_id AND
|
|
post_number = :actual_read_post_number AND
|
|
NOT EXISTS (
|
|
SELECT 1 FROM post_timings pt1
|
|
WHERE pt1.topic_id = pt.topic_id AND
|
|
pt1.post_number = :pretend_read_post_number AND
|
|
pt1.user_id = pt.user_id
|
|
)
|
|
",
|
|
pretend_read_post_number: pretend_read_post_number,
|
|
topic_id: topic_id,
|
|
actual_read_post_number: actual_read_post_number
|
|
)
|
|
|
|
TopicUser.ensure_consistency!(topic_id)
|
|
end
|
|
|
|
def self.record_new_timing(args)
|
|
row_count = DB.exec("INSERT INTO post_timings (topic_id, user_id, post_number, msecs)
|
|
SELECT :topic_id, :user_id, :post_number, :msecs
|
|
ON CONFLICT DO NOTHING",
|
|
args)
|
|
|
|
# concurrency is hard, we are not running serialized so this can possibly
|
|
# still happen, if it happens we just don't care, its an invalid record anyway
|
|
return if row_count == 0
|
|
Post.where(['topic_id = :topic_id and post_number = :post_number', args]).update_all 'reads = reads + 1'
|
|
|
|
return if Topic.exists?(id: args[:topic_id], archetype: Archetype.private_message)
|
|
UserStat.where(user_id: args[:user_id]).update_all 'posts_read_count = posts_read_count + 1'
|
|
end
|
|
|
|
# Increases a timer if a row exists, otherwise create it
|
|
def self.record_timing(args)
|
|
rows = DB.exec(<<~SQL, args)
|
|
UPDATE post_timings
|
|
SET msecs = msecs + :msecs
|
|
WHERE topic_id = :topic_id
|
|
AND user_id = :user_id
|
|
AND post_number = :post_number
|
|
SQL
|
|
|
|
record_new_timing(args) if rows == 0
|
|
end
|
|
|
|
def self.destroy_last_for(user, topic_id: nil, topic: nil)
|
|
topic ||= Topic.find(topic_id)
|
|
post_number = user.whisperer? ? topic.highest_staff_post_number : topic.highest_post_number
|
|
|
|
last_read = post_number - 1
|
|
|
|
PostTiming.transaction do
|
|
PostTiming.where("topic_id = ? AND user_id = ? AND post_number > ?", topic.id, user.id, last_read).delete_all
|
|
if last_read < 1
|
|
last_read = nil
|
|
end
|
|
|
|
TopicUser.where(user_id: user.id, topic_id: topic.id).update_all(
|
|
last_read_post_number: last_read
|
|
)
|
|
|
|
topic.posts.find_by(post_number: post_number).decrement!(:reads)
|
|
|
|
if topic.private_message?
|
|
set_minimum_first_unread_pm!(topic: topic, user_id: user.id, date: topic.updated_at)
|
|
else
|
|
set_minimum_first_unread!(user_id: user.id, date: topic.updated_at)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.destroy_for(user_id, topic_ids)
|
|
PostTiming.transaction do
|
|
PostTiming
|
|
.where('user_id = ? and topic_id in (?)', user_id, topic_ids)
|
|
.delete_all
|
|
|
|
TopicUser
|
|
.where('user_id = ? and topic_id in (?)', user_id, topic_ids)
|
|
.delete_all
|
|
|
|
Post.where(topic_id: topic_ids).update_all('reads = reads - 1')
|
|
|
|
date = Topic.listable_topics.where(id: topic_ids).minimum(:updated_at)
|
|
|
|
if date
|
|
set_minimum_first_unread!(user_id: user_id, date: date)
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.set_minimum_first_unread_pm!(topic:, user_id:, date:)
|
|
if topic.topic_allowed_users.exists?(user_id: user_id)
|
|
UserStat.where("first_unread_pm_at > ? AND user_id = ?", date, user_id)
|
|
.update_all(first_unread_pm_at: date)
|
|
else
|
|
DB.exec(<<~SQL, date: date, user_id: user_id, topic_id: topic.id)
|
|
UPDATE group_users gu
|
|
SET first_unread_pm_at = :date
|
|
FROM (
|
|
SELECT
|
|
gu2.user_id,
|
|
gu2.group_id
|
|
FROM group_users gu2
|
|
INNER JOIN topic_allowed_groups tag ON tag.group_id = gu2.group_id AND tag.topic_id = :topic_id
|
|
WHERE gu2.user_id = :user_id
|
|
) Y
|
|
WHERE gu.user_id = Y.user_id AND gu.group_id = Y.group_id
|
|
SQL
|
|
end
|
|
end
|
|
|
|
def self.set_minimum_first_unread!(user_id:, date:)
|
|
DB.exec(<<~SQL, date: date, user_id: user_id)
|
|
UPDATE user_stats
|
|
SET first_unread_at = :date
|
|
WHERE first_unread_at > :date AND
|
|
user_id = :user_id
|
|
SQL
|
|
end
|
|
|
|
MAX_READ_TIME_PER_BATCH = 60 * 1000.0
|
|
|
|
def self.process_timings(current_user, topic_id, topic_time, timings, opts = {})
|
|
UserStat.update_time_read!(current_user.id)
|
|
|
|
max_time_per_post = ((Time.now - current_user.created_at) * 1000.0)
|
|
max_time_per_post = MAX_READ_TIME_PER_BATCH if max_time_per_post > MAX_READ_TIME_PER_BATCH
|
|
|
|
highest_seen = 1
|
|
new_posts_read = 0
|
|
|
|
join_table = []
|
|
|
|
i = timings.length
|
|
while i > 0
|
|
i -= 1
|
|
timings[i][1] = max_time_per_post if timings[i][1] > max_time_per_post
|
|
timings.delete_at(i) if timings[i][0] < 1
|
|
end
|
|
|
|
timings.each_with_index do |(post_number, time), index|
|
|
|
|
join_table << "SELECT #{topic_id.to_i} topic_id, #{post_number.to_i} post_number,
|
|
#{current_user.id.to_i} user_id, #{time.to_i} msecs, #{index} idx"
|
|
|
|
highest_seen = post_number.to_i > highest_seen ?
|
|
post_number.to_i : highest_seen
|
|
end
|
|
|
|
if join_table.length > 0
|
|
sql = <<~SQL
|
|
UPDATE post_timings t
|
|
SET msecs = LEAST(t.msecs::bigint + x.msecs, 2^31 - 1)
|
|
FROM (#{join_table.join(" UNION ALL ")}) x
|
|
WHERE x.topic_id = t.topic_id AND
|
|
x.post_number = t.post_number AND
|
|
x.user_id = t.user_id
|
|
RETURNING x.idx
|
|
SQL
|
|
|
|
existing = Set.new(DB.query_single(sql))
|
|
|
|
sql = <<~SQL
|
|
SELECT 1 FROM topics
|
|
WHERE deleted_at IS NULL AND
|
|
archetype = 'regular' AND
|
|
id = :topic_id
|
|
SQL
|
|
|
|
is_regular = DB.exec(sql, topic_id: topic_id) == 1
|
|
new_posts_read = timings.size - existing.size if is_regular
|
|
|
|
timings.each_with_index do |(post_number, time), index|
|
|
unless existing.include?(index)
|
|
PostTiming.record_new_timing(topic_id: topic_id,
|
|
post_number: post_number,
|
|
user_id: current_user.id,
|
|
msecs: time)
|
|
end
|
|
end
|
|
end
|
|
|
|
total_changed = 0
|
|
if timings.length > 0
|
|
total_changed = Notification.mark_posts_read(current_user, topic_id, timings.map { |t| t[0] })
|
|
end
|
|
|
|
topic_time = max_time_per_post if topic_time > max_time_per_post
|
|
|
|
TopicUser.update_last_read(current_user, topic_id, highest_seen, new_posts_read, topic_time, opts)
|
|
TopicGroup.update_last_read(current_user, topic_id, highest_seen)
|
|
|
|
if total_changed > 0
|
|
current_user.reload
|
|
current_user.publish_notifications_state
|
|
end
|
|
end
|
|
end
|
|
|
|
# == Schema Information
|
|
#
|
|
# Table name: post_timings
|
|
#
|
|
# topic_id :integer not null
|
|
# post_number :integer not null
|
|
# user_id :integer not null
|
|
# msecs :integer not null
|
|
#
|
|
# Indexes
|
|
#
|
|
# index_post_timings_on_user_id (user_id)
|
|
# post_timings_unique (topic_id,post_number,user_id) UNIQUE
|
|
#
|