PageRenderTime 24ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/app/models/topic_tracking_state.rb

https://gitlab.com/Ruwan-Ranganath/discourse
Ruby | 237 lines | 175 code | 45 blank | 17 comment | 15 complexity | d7f729f36d5042712614cd353ac4cdcc MD5 | raw file
  1. # this class is used to mirror unread and new status back to end users
  2. # in JavaScript there is a mirror class that is kept in-sync using the mssage bus
  3. # the allows end users to always know which topics have unread posts in them
  4. # and which topics are new
  5. class TopicTrackingState
  6. include ActiveModel::SerializerSupport
  7. CHANNEL = "/user-tracking"
  8. attr_accessor :user_id,
  9. :topic_id,
  10. :highest_post_number,
  11. :last_read_post_number,
  12. :created_at,
  13. :category_id,
  14. :notification_level
  15. def self.publish_new(topic)
  16. message = {
  17. topic_id: topic.id,
  18. message_type: "new_topic",
  19. payload: {
  20. last_read_post_number: nil,
  21. highest_post_number: 1,
  22. created_at: topic.created_at,
  23. topic_id: topic.id,
  24. category_id: topic.category_id,
  25. archetype: topic.archetype
  26. }
  27. }
  28. group_ids = topic.category && topic.category.secure_group_ids
  29. MessageBus.publish("/new", message.as_json, group_ids: group_ids)
  30. publish_read(topic.id, 1, topic.user_id)
  31. end
  32. def self.publish_latest(topic)
  33. return unless topic.archetype == "regular"
  34. message = {
  35. topic_id: topic.id,
  36. message_type: "latest",
  37. payload: {
  38. bumped_at: topic.bumped_at,
  39. topic_id: topic.id,
  40. category_id: topic.category_id,
  41. archetype: topic.archetype
  42. }
  43. }
  44. group_ids = topic.category && topic.category.secure_group_ids
  45. MessageBus.publish("/latest", message.as_json, group_ids: group_ids)
  46. end
  47. def self.publish_unread(post)
  48. # TODO at high scale we are going to have to defer this,
  49. # perhaps cut down to users that are around in the last 7 days as well
  50. #
  51. group_ids = post.topic.category && post.topic.category.secure_group_ids
  52. TopicUser
  53. .tracking(post.topic_id)
  54. .select([:user_id,:last_read_post_number, :notification_level])
  55. .each do |tu|
  56. message = {
  57. topic_id: post.topic_id,
  58. message_type: "unread",
  59. payload: {
  60. last_read_post_number: tu.last_read_post_number,
  61. highest_post_number: post.post_number,
  62. created_at: post.created_at,
  63. topic_id: post.topic_id,
  64. category_id: post.topic.category_id,
  65. notification_level: tu.notification_level,
  66. archetype: post.topic.archetype
  67. }
  68. }
  69. MessageBus.publish("/unread/#{tu.user_id}", message.as_json, group_ids: group_ids)
  70. end
  71. end
  72. def self.publish_recover(topic)
  73. group_ids = topic.category && topic.category.secure_group_ids
  74. message = {
  75. topic_id: topic.id,
  76. message_type: "recover",
  77. payload: {
  78. topic_id: topic.id,
  79. }
  80. }
  81. MessageBus.publish("/recover", message.as_json, group_ids: group_ids)
  82. end
  83. def self.publish_delete(topic)
  84. group_ids = topic.category && topic.category.secure_group_ids
  85. message = {
  86. topic_id: topic.id,
  87. message_type: "delete",
  88. payload: {
  89. topic_id: topic.id,
  90. }
  91. }
  92. MessageBus.publish("/delete", message.as_json, group_ids: group_ids)
  93. end
  94. def self.publish_read(topic_id, last_read_post_number, user_id, notification_level=nil)
  95. highest_post_number = Topic.where(id: topic_id).pluck(:highest_post_number).first
  96. message = {
  97. topic_id: topic_id,
  98. message_type: "read",
  99. payload: {
  100. last_read_post_number: last_read_post_number,
  101. highest_post_number: highest_post_number,
  102. topic_id: topic_id,
  103. notification_level: notification_level
  104. }
  105. }
  106. MessageBus.publish("/unread/#{user_id}", message.as_json, user_ids: [user_id])
  107. end
  108. def self.treat_as_new_topic_clause
  109. User.where("GREATEST(CASE
  110. WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :always THEN u.created_at
  111. WHEN COALESCE(uo.new_topic_duration_minutes, :default_duration) = :last_visit THEN COALESCE(u.previous_visit_at,u.created_at)
  112. ELSE (:now::timestamp - INTERVAL '1 MINUTE' * COALESCE(uo.new_topic_duration_minutes, :default_duration))
  113. END, us.new_since, :min_date)",
  114. now: DateTime.now,
  115. last_visit: User::NewTopicDuration::LAST_VISIT,
  116. always: User::NewTopicDuration::ALWAYS,
  117. default_duration: SiteSetting.default_other_new_topic_duration_minutes,
  118. min_date: Time.at(SiteSetting.min_new_topics_time).to_datetime
  119. ).where_values[0]
  120. end
  121. def self.report(user_id, topic_id = nil)
  122. # Sam: this is a hairy report, in particular I need custom joins and fancy conditions
  123. # Dropping to sql_builder so I can make sense of it.
  124. #
  125. # Keep in mind, we need to be able to filter on a GROUP of users, and zero in on topic
  126. # all our existing scope work does not do this
  127. #
  128. # This code needs to be VERY efficient as it is triggered via the message bus and may steal
  129. # cycles from usual requests
  130. #
  131. #
  132. sql = report_raw_sql(topic_id: topic_id, skip_unread: true, skip_order: true)
  133. sql << "\nUNION ALL\n\n"
  134. sql << report_raw_sql(topic_id: topic_id, skip_new: true, skip_order: true)
  135. SqlBuilder.new(sql)
  136. .map_exec(TopicTrackingState, user_id: user_id, topic_id: topic_id)
  137. end
  138. def self.report_raw_sql(opts=nil)
  139. unread =
  140. if opts && opts[:skip_unread]
  141. "1=0"
  142. else
  143. TopicQuery.unread_filter(Topic).where_values.join(" AND ")
  144. end
  145. new =
  146. if opts && opts[:skip_new]
  147. "1=0"
  148. else
  149. TopicQuery.new_filter(Topic, "xxx").where_values.join(" AND ").gsub!("'xxx'", treat_as_new_topic_clause)
  150. end
  151. select = (opts && opts[:select]) || "
  152. u.id AS user_id,
  153. topics.id AS topic_id,
  154. topics.created_at,
  155. highest_post_number,
  156. last_read_post_number,
  157. c.id AS category_id,
  158. tu.notification_level"
  159. sql = <<SQL
  160. SELECT #{select}
  161. FROM topics
  162. JOIN users u on u.id = :user_id
  163. JOIN user_stats AS us ON us.user_id = u.id
  164. JOIN user_options AS uo ON uo.user_id = u.id
  165. JOIN categories c ON c.id = topics.category_id
  166. LEFT JOIN topic_users tu ON tu.topic_id = topics.id AND tu.user_id = u.id
  167. WHERE u.id = :user_id AND
  168. topics.archetype <> 'private_message' AND
  169. ((#{unread}) OR (#{new})) AND
  170. (topics.visible OR u.admin OR u.moderator) AND
  171. topics.deleted_at IS NULL AND
  172. ( NOT c.read_restricted OR u.admin OR category_id IN (
  173. SELECT c2.id FROM categories c2
  174. JOIN category_groups cg ON cg.category_id = c2.id
  175. JOIN group_users gu ON gu.user_id = :user_id AND cg.group_id = gu.group_id
  176. WHERE c2.read_restricted )
  177. )
  178. AND NOT EXISTS( SELECT 1 FROM category_users cu
  179. WHERE last_read_post_number IS NULL AND
  180. cu.user_id = :user_id AND
  181. cu.category_id = topics.category_id AND
  182. cu.notification_level = #{CategoryUser.notification_levels[:muted]})
  183. SQL
  184. if opts && opts[:topic_id]
  185. sql << " AND topics.id = :topic_id"
  186. end
  187. unless opts && opts[:skip_order]
  188. sql << " ORDER BY topics.bumped_at DESC"
  189. end
  190. sql
  191. end
  192. end