discourse/app/models/topic_user.rb

198 lines
7.0 KiB
Ruby

class TopicUser < ActiveRecord::Base
belongs_to :user
belongs_to :topic
module NotificationLevel
WATCHING = 3
TRACKING = 2
REGULAR = 1
MUTED = 0
end
module NotificationReasons
CREATED_TOPIC = 1
USER_CHANGED = 2
USER_INTERACTED = 3
CREATED_POST = 4
end
def self.auto_track(user_id, topic_id, reason)
if exec_sql("select 1 from topic_users where user_id = ? and topic_id = ? and notifications_reason_id is null", user_id, topic_id).count == 1
self.change(user_id, topic_id,
notification_level: NotificationLevel::TRACKING,
notifications_reason_id: reason
)
MessageBus.publish("/topic/#{topic_id}", {
notification_level_change: NotificationLevel::TRACKING,
notifications_reason_id: reason
}, user_ids: [user_id])
end
end
# Find the information specific to a user in a forum topic
def self.lookup_for(user, topics)
# If the user isn't logged in, there's no last read posts
return {} if user.blank?
return {} if topics.blank?
topic_ids = topics.map {|ft| ft.id}
create_lookup(TopicUser.where(topic_id: topic_ids, user_id: user.id))
end
def self.create_lookup(topic_users)
topic_users = topic_users.to_a
result = {}
return result if topic_users.blank?
topic_users.each do |ftu|
result[ftu.topic_id] = ftu
end
result
end
def self.get(topic,user)
if Topic === topic
topic = topic.id
end
if User === user
user = user.id
end
TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
end
# Change attributes for a user (creates a record when none is present). First it tries an update
# since there's more likely to be an existing record than not. If the update returns 0 rows affected
# it then creates the row instead.
def self.change(user_id, topic_id, attrs)
# Sometimes people pass objs instead of the ids. We can handle that.
topic_id = topic_id.id if topic_id.is_a?(Topic)
user_id = user_id.id if user_id.is_a?(User)
TopicUser.transaction do
attrs = attrs.dup
attrs[:starred_at] = DateTime.now if attrs[:starred_at].nil? && attrs[:starred]
if attrs[:notification_level]
attrs[:notifications_changed_at] ||= DateTime.now
attrs[:notifications_reason_id] ||= TopicUser::NotificationReasons::USER_CHANGED
end
attrs_array = attrs.to_a
attrs_sql = attrs_array.map {|t| "#{t[0]} = ?"}.join(", ")
vals = attrs_array.map {|t| t[1]}
rows = TopicUser.update_all([attrs_sql, *vals], ["topic_id = ? and user_id = ?", topic_id.to_i, user_id])
if rows == 0
now = DateTime.now
auto_track_after = self.exec_sql("select auto_track_topics_after_msecs from users where id = ?", user_id).values[0][0]
auto_track_after ||= SiteSetting.auto_track_topics_after
auto_track_after = auto_track_after.to_i
if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed] || 0)
attrs[:notification_level] ||= TopicUser::NotificationLevel::TRACKING
end
TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id.to_i, first_visited_at: now ,last_visited_at: now))
end
end
rescue ActiveRecord::RecordNotUnique
# In case of a race condition to insert, do nothing
end
def self.track_visit!(topic,user)
now = DateTime.now
rows = exec_sql_row_count(
"update topic_users set last_visited_at=? where topic_id=? and user_id=?",
now, topic.id, user.id
)
if rows == 0
exec_sql('insert into topic_users(topic_id, user_id, last_visited_at, first_visited_at)
values(?,?,?,?)',
topic.id, user.id, now, now)
end
end
# Update the last read and the last seen post count, but only if it doesn't exist.
# This would be a lot easier if psql supported some kind of upsert
def self.update_last_read(user, topic_id, post_number, msecs)
return if post_number.blank?
msecs = 0 if msecs.to_i < 0
args = {
user_id: user.id,
topic_id: topic_id,
post_number: post_number,
now: DateTime.now,
msecs: msecs,
tracking: TopicUser::NotificationLevel::TRACKING,
threshold: SiteSetting.auto_track_topics_after
}
rows = exec_sql("UPDATE topic_users
SET
last_read_post_number = greatest(:post_number, tu.last_read_post_number),
seen_post_count = t.highest_post_number,
total_msecs_viewed = tu.total_msecs_viewed + :msecs,
notification_level =
case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
coalesce(u.auto_track_topics_after_msecs,:threshold) and
coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
:tracking
else
tu.notification_level
end
FROM topic_users tu
join topics t on t.id = tu.topic_id
join users u on u.id = :user_id
WHERE
tu.topic_id = topic_users.topic_id AND
tu.user_id = topic_users.user_id AND
tu.topic_id = :topic_id AND
tu.user_id = :user_id
RETURNING
topic_users.notification_level, tu.notification_level old_level
",
args).values
if rows.length == 1
before = rows[0][1].to_i
after = rows[0][0].to_i
if before != after
MessageBus.publish("/topic/#{topic_id}", {notification_level_change: after}, user_ids: [user.id])
end
end
if rows.length == 0
self
args[:tracking] = TopicUser::NotificationLevel::TRACKING
args[:regular] = TopicUser::NotificationLevel::REGULAR
args[:site_setting] = SiteSetting.auto_track_topics_after
exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now,
case when coalesce(u.auto_track_topics_after_msecs, :site_setting) = 0 then :tracking else :regular end
FROM topics AS ft
JOIN users u on u.id = :user_id
WHERE ft.id = :topic_id
AND NOT EXISTS(SELECT 1
FROM topic_users AS ftu
WHERE ftu.user_id = :user_id and ftu.topic_id = :topic_id)",
args)
end
end
end