Commit f2e491e7 by Ibrahim Awwal

Merge pull request #6 from rll/unread_count_decoupled

Unread comment tracking
parents 33f76820 b3ad06e9
...@@ -3,7 +3,14 @@ get "#{APIPREFIX}/threads" do # retrieve threads by course ...@@ -3,7 +3,14 @@ get "#{APIPREFIX}/threads" do # retrieve threads by course
end end
get "#{APIPREFIX}/threads/:thread_id" do |thread_id| get "#{APIPREFIX}/threads/:thread_id" do |thread_id|
CommentThread.find(thread_id).to_hash(recursive: bool_recursive).to_json thread = CommentThread.find(thread_id)
if params["user_id"] and bool_mark_as_read
user = User.only([:id, :read_states]).find_by(external_id: params["user_id"])
user.mark_as_read(thread) if user
end
thread.to_hash(recursive: bool_recursive, user_id: params["user_id"]).to_json
end end
put "#{APIPREFIX}/threads/:thread_id" do |thread_id| put "#{APIPREFIX}/threads/:thread_id" do |thread_id|
......
...@@ -40,7 +40,7 @@ get "#{APIPREFIX}/search/threads" do ...@@ -40,7 +40,7 @@ get "#{APIPREFIX}/search/threads" do
num_pages = results.total_pages num_pages = results.total_pages
page = [num_pages, [1, page].max].min page = [num_pages, [1, page].max].min
{ {
collection: results.map{|t| CommentThread.search_result_to_hash(t, recursive: bool_recursive)}, collection: results.map{|t| CommentThread.search_result_to_hash(t, recursive: bool_recursive, user_id: params[:user_id])},
num_pages: num_pages, num_pages: num_pages,
page: page, page: page,
}.to_json }.to_json
......
...@@ -44,10 +44,7 @@ get "#{APIPREFIX}/users/:user_id/active_threads" do |user_id| ...@@ -44,10 +44,7 @@ get "#{APIPREFIX}/users/:user_id/active_threads" do |user_id|
end end
put "#{APIPREFIX}/users/:user_id" do |user_id| put "#{APIPREFIX}/users/:user_id" do |user_id|
user = User.where(external_id: user_id).first user = User.find_or_create_by(external_id: user_id)
if not user
user = User.new(external_id: user_id)
end
user.update_attributes(params.slice(*%w[username email default_sort_key])) user.update_attributes(params.slice(*%w[username email default_sort_key]))
if user.errors.any? if user.errors.any?
error 400, user.errors.full_messages.to_json error 400, user.errors.full_messages.to_json
......
...@@ -51,6 +51,10 @@ helpers do ...@@ -51,6 +51,10 @@ helpers do
value_to_boolean params["recursive"] value_to_boolean params["recursive"]
end end
def bool_mark_as_read
value_to_boolean params["mark_as_read"]
end
def bool_complete def bool_complete
value_to_boolean params["complete"] value_to_boolean params["complete"]
end end
...@@ -79,7 +83,7 @@ helpers do ...@@ -79,7 +83,7 @@ helpers do
cached_results = Sinatra::Application.cache.get(memcached_key) cached_results = Sinatra::Application.cache.get(memcached_key)
if cached_results if cached_results
return { return {
collection: cached_results[:collection_ids].map{|id| CommentThread.find(id).to_hash(recursive: bool_recursive)}, collection: cached_results[:collection_ids].map{|id| CommentThread.find(id).to_hash(recursive: bool_recursive, user_id: params["user_id"])},
num_pages: cached_results[:num_pages], num_pages: cached_results[:num_pages],
page: cached_results[:page], page: cached_results[:page],
}.to_json }.to_json
...@@ -119,7 +123,7 @@ helpers do ...@@ -119,7 +123,7 @@ helpers do
Sinatra::Application.cache.set(memcached_key, cached_results, CommentService.config[:cache_timeout][:threads_query].to_i) Sinatra::Application.cache.set(memcached_key, cached_results, CommentService.config[:cache_timeout][:threads_query].to_i)
end end
{ {
collection: paged_comment_threads.map{|t| t.to_hash(recursive: bool_recursive)}, collection: paged_comment_threads.map{|t| t.to_hash(recursive: bool_recursive, user_id: params["user_id"])},
num_pages: num_pages, num_pages: num_pages,
page: page, page: page,
}.to_json }.to_json
......
...@@ -165,8 +165,43 @@ class CommentThread < Content ...@@ -165,8 +165,43 @@ class CommentThread < Content
if params[:recursive] if params[:recursive]
doc = doc.merge("children" => root_comments.map{|c| c.to_hash(recursive: true)}) doc = doc.merge("children" => root_comments.map{|c| c.to_hash(recursive: true)})
end end
doc = doc.merge("comments_count" => comments.count)
comments_count = comments.count
if params[:user_id]
user = User.find_or_create_by(external_id: params[:user_id])
read_state = user.read_states.where(course_id: self.course_id).first
last_read_time = read_state.last_read_times[self.id.to_s] if read_state
# comments created by the user are excluded in the count
# this is rather like a hack but it avoids the following situation:
# when you reply to a thread and while you are editing,
# other people also replied to the thread. Now if we simply
# update the last_read_time, then the other people's replies
# will not be included in the unread_count; if we leave it
# that way, then your own comment will be included in the
# unread count
if last_read_time
unread_count = self.comments.where(
:updated_at => {:$gte => last_read_time},
:author_id => {:$ne => params[:user_id]},
).count
read = last_read_time >= self.updated_at
else
unread_count = self.comments.where(:author_id => {:$ne => params[:user_id]}).count
read = false
end
else
# If there's no user, say it's unread and all comments are unread
unread_count = comments_count
read = false
end
doc = doc.merge("unread_comments_count" => unread_count)
.merge("read" => read)
.merge("comments_count" => comments_count)
doc doc
end end
def self.tag_name_valid?(tag) def self.tag_name_valid?(tag)
......
...@@ -8,6 +8,7 @@ class User ...@@ -8,6 +8,7 @@ class User
field :email, type: String field :email, type: String
field :default_sort_key, type: String, default: "date" field :default_sort_key, type: String, default: "date"
embeds_many :read_states
has_many :comments, inverse_of: :author has_many :comments, inverse_of: :author
has_many :comment_threads, inverse_of: :author has_many :comment_threads, inverse_of: :author
has_many :activities, class_name: "Notification", inverse_of: :actor has_many :activities, class_name: "Notification", inverse_of: :actor
...@@ -101,4 +102,23 @@ class User ...@@ -101,4 +102,23 @@ class User
subscription subscription
end end
def mark_as_read(thread)
read_state = read_states.find_or_create_by(course_id: thread.course_id)
read_state.last_read_times[thread.id] = Time.now.utc
read_state.save
end
end
class ReadState
include Mongoid::Document
field :course_id, type: String
field :last_read_times, type: Hash, default: {}
embedded_in :user
validates :course_id, uniqueness: true, presence: true
def to_hash
to_json
end
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment