2013-05-21 16:39:51 +10:00
# this class is used to mirror unread and new status back to end users
# in JavaScript there is a mirror class that is kept in-sync using the mssage bus
# the allows end users to always know which topics have unread posts in them
# and which topics are new
2013-05-29 18:11:04 +10:00
class TopicTrackingState
2013-05-21 16:39:51 +10:00
2013-05-24 20:58:26 +10:00
include ActiveModel :: SerializerSupport
2013-05-21 16:39:51 +10:00
CHANNEL = " /user-tracking "
2014-02-26 15:37:42 -05:00
attr_accessor :user_id ,
:topic_id ,
:highest_post_number ,
:last_read_post_number ,
:created_at ,
2014-07-16 15:39:39 -04:00
:category_id ,
2014-02-26 15:37:42 -05:00
:notification_level
2013-05-23 15:21:07 +10:00
2013-05-29 18:11:04 +10:00
def self . publish_new ( topic )
message = {
topic_id : topic . id ,
message_type : " new_topic " ,
payload : {
last_read_post_number : nil ,
highest_post_number : 1 ,
created_at : topic . created_at ,
2014-06-18 11:21:40 +10:00
topic_id : topic . id ,
category_id : topic . category_id
2013-05-29 18:11:04 +10:00
}
}
group_ids = topic . category && topic . category . secure_group_ids
2015-05-04 12:21:00 +10:00
MessageBus . publish ( " /new " , message . as_json , group_ids : group_ids )
2013-05-29 18:11:04 +10:00
publish_read ( topic . id , 1 , topic . user_id )
end
2014-08-05 13:27:34 +10:00
def self . publish_latest ( topic )
return unless topic . archetype == " regular "
message = {
topic_id : topic . id ,
message_type : " latest " ,
payload : {
bumped_at : topic . bumped_at ,
topic_id : topic . id ,
category_id : topic . category_id
}
}
group_ids = topic . category && topic . category . secure_group_ids
2015-05-04 12:21:00 +10:00
MessageBus . publish ( " /latest " , message . as_json , group_ids : group_ids )
2014-08-05 13:27:34 +10:00
end
2013-05-29 18:11:04 +10:00
def self . publish_unread ( post )
# TODO at high scale we are going to have to defer this,
# perhaps cut down to users that are around in the last 7 days as well
#
group_ids = post . topic . category && post . topic . category . secure_group_ids
TopicUser
. tracking ( post . topic_id )
2014-06-06 15:17:02 +10:00
. select ( [ :user_id , :last_read_post_number , :notification_level ] )
2013-05-29 18:11:04 +10:00
. each do | tu |
message = {
topic_id : post . topic_id ,
message_type : " unread " ,
payload : {
last_read_post_number : tu . last_read_post_number ,
highest_post_number : post . post_number ,
created_at : post . created_at ,
2014-06-06 15:17:02 +10:00
topic_id : post . topic_id ,
2015-09-21 10:36:20 +10:00
category_id : post . topic . category_id ,
2014-06-06 15:17:02 +10:00
notification_level : tu . notification_level
2013-05-29 18:11:04 +10:00
}
}
2015-05-04 12:21:00 +10:00
MessageBus . publish ( " /unread/ #{ tu . user_id } " , message . as_json , group_ids : group_ids )
2013-05-21 16:39:51 +10:00
end
2013-11-25 17:37:51 +11:00
2013-05-21 16:39:51 +10:00
end
2014-02-26 15:37:42 -05:00
def self . publish_read ( topic_id , last_read_post_number , user_id , notification_level = nil )
2013-05-30 16:19:12 +10:00
2013-11-25 17:37:51 +11:00
highest_post_number = Topic . where ( id : topic_id ) . pluck ( :highest_post_number ) . first
2013-05-30 16:19:12 +10:00
2013-11-25 17:37:51 +11:00
message = {
topic_id : topic_id ,
message_type : " read " ,
payload : {
last_read_post_number : last_read_post_number ,
highest_post_number : highest_post_number ,
2014-02-26 15:37:42 -05:00
topic_id : topic_id ,
notification_level : notification_level
2013-05-30 16:19:12 +10:00
}
2013-11-25 17:37:51 +11:00
}
2015-05-04 12:21:00 +10:00
MessageBus . publish ( " /unread/ #{ user_id } " , message . as_json , user_ids : [ user_id ] )
2013-05-30 16:19:12 +10:00
2013-05-21 16:39:51 +10:00
end
2013-05-23 15:21:07 +10:00
def self . treat_as_new_topic_clause
2014-03-03 16:11:59 -05:00
User . where ( " GREATEST(CASE
2016-02-18 16:57:22 +11:00
WHEN COALESCE ( uo . new_topic_duration_minutes , :default_duration ) = :always THEN u . created_at
WHEN COALESCE ( uo . new_topic_duration_minutes , :default_duration ) = :last_visit THEN COALESCE ( u . previous_visit_at , u . created_at )
ELSE ( :now :: timestamp - INTERVAL '1 MINUTE' * COALESCE ( uo . new_topic_duration_minutes , :default_duration ) )
2015-09-07 11:57:50 +10:00
END , us . new_since , :min_date ) " ,
2013-05-23 15:21:07 +10:00
now : DateTime . now ,
last_visit : User :: NewTopicDuration :: LAST_VISIT ,
always : User :: NewTopicDuration :: ALWAYS ,
2015-09-07 11:57:50 +10:00
default_duration : SiteSetting . default_other_new_topic_duration_minutes ,
min_date : Time . at ( SiteSetting . min_new_topics_time ) . to_datetime
2013-05-23 15:21:07 +10:00
) . where_values [ 0 ]
2013-05-21 16:39:51 +10:00
end
2015-07-21 21:48:07 +10:00
def self . report ( user_id , topic_id = nil )
2013-05-21 16:39:51 +10:00
2013-05-23 15:21:07 +10:00
# Sam: this is a hairy report, in particular I need custom joins and fancy conditions
# Dropping to sql_builder so I can make sense of it.
#
# Keep in mind, we need to be able to filter on a GROUP of users, and zero in on topic
# all our existing scope work does not do this
#
# This code needs to be VERY efficient as it is triggered via the message bus and may steal
# cycles from usual requests
#
2015-09-07 11:57:50 +10:00
#
2015-09-29 11:55:09 +10:00
sql = report_raw_sql ( topic_id : topic_id , skip_unread : true , skip_order : true )
sql << " \n UNION ALL \n \n "
sql << report_raw_sql ( topic_id : topic_id , skip_new : true , skip_order : true )
2015-09-07 11:57:50 +10:00
SqlBuilder . new ( sql )
. map_exec ( TopicTrackingState , user_id : user_id , topic_id : topic_id )
end
def self . report_raw_sql ( opts = nil )
unread =
if opts && opts [ :skip_unread ]
" 1=0 "
else
TopicQuery . unread_filter ( Topic ) . where_values . join ( " AND " )
end
new =
if opts && opts [ :skip_new ]
" 1=0 "
else
TopicQuery . new_filter ( Topic , " xxx " ) . where_values . join ( " AND " ) . gsub! ( " 'xxx' " , treat_as_new_topic_clause )
end
select = ( opts && opts [ :select ] ) || "
u . id AS user_id ,
2015-07-21 21:53:54 +10:00
topics . id AS topic_id ,
2014-02-26 15:37:42 -05:00
topics . created_at ,
highest_post_number ,
last_read_post_number ,
2015-07-21 21:53:54 +10:00
c . id AS category_id ,
2015-09-07 11:57:50 +10:00
tu . notification_level "
sql = <<SQL
SELECT #{select}
2015-07-21 22:45:04 +10:00
FROM topics
JOIN users u on u . id = :user_id
JOIN user_stats AS us ON us . user_id = u . id
2016-02-18 16:57:22 +11:00
JOIN user_options AS uo ON uo . user_id = u . id
2015-07-21 22:45:04 +10:00
JOIN categories c ON c . id = topics . category_id
2015-07-21 21:53:54 +10:00
LEFT JOIN topic_users tu ON tu . topic_id = topics . id AND tu . user_id = u . id
WHERE u . id = :user_id AND
topics . archetype < > 'private_message' AND
( ( #{unread}) OR (#{new})) AND
2013-05-23 15:21:07 +10:00
( topics . visible OR u . admin OR u . moderator ) AND
2015-07-21 21:53:54 +10:00
topics . deleted_at IS NULL AND
2015-07-21 22:45:04 +10:00
( NOT c . read_restricted OR u . admin OR category_id IN (
2015-07-21 21:53:54 +10:00
SELECT c2 . id FROM categories c2
JOIN category_groups cg ON cg . category_id = c2 . id
2015-07-21 22:26:51 +10:00
JOIN group_users gu ON gu . user_id = :user_id AND cg . group_id = gu . group_id
2015-07-21 21:53:54 +10:00
WHERE c2 . read_restricted )
)
AND NOT EXISTS ( SELECT 1 FROM category_users cu
WHERE last_read_post_number IS NULL AND
cu . user_id = :user_id AND
cu . category_id = topics . category_id AND
cu . notification_level = #{CategoryUser.notification_levels[:muted]})
2015-07-21 21:48:07 +10:00
2013-05-23 15:21:07 +10:00
SQL
2015-09-07 11:57:50 +10:00
if opts && opts [ :topic_id ]
2015-07-21 21:53:54 +10:00
sql << " AND topics.id = :topic_id "
2013-05-23 15:21:07 +10:00
end
2014-09-10 22:19:24 +10:00
2015-09-29 11:55:09 +10:00
unless opts && opts [ :skip_order ]
sql << " ORDER BY topics.bumped_at DESC "
end
sql
2013-05-21 16:39:51 +10:00
end
end