mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 07:34:18 +08:00
301a0fa54e
Before this commit, the presence state of users were stored on the server side and any updates to the state meant we had to publish the entire state to the clients. Also, the way the state of users were stored on the server side meant we didn't have a way to differentiate between replying users and whispering users. In this redesign, we decided to move the tracking of users state to the client side and have the server publish client events instead. As a result of this change, we're able to remove the number of opened connections needed to track presence and also reduce the payload that is sent for each event. At the same time, we've also improved on the restrictions when publishing message_bus messages. Users that do not have permission to see certain events will not receive messages for those events.
174 lines
4.9 KiB
Ruby
174 lines
4.9 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-presence
|
|
# about: Show which users are writing a reply to a topic
|
|
# version: 2.0
|
|
# authors: André Pereira, David Taylor, tgxworld
|
|
# url: https://github.com/discourse/discourse/tree/master/plugins/discourse-presence
|
|
|
|
enabled_site_setting :presence_enabled
|
|
hide_plugin if self.respond_to?(:hide_plugin)
|
|
|
|
register_asset 'stylesheets/presence.scss'
|
|
|
|
PLUGIN_NAME ||= -"discourse-presence"
|
|
|
|
after_initialize do
|
|
|
|
MessageBus.register_client_message_filter('/presence/') do |message|
|
|
published_at = message.data["published_at"]
|
|
|
|
if published_at
|
|
(Time.zone.now.to_i - published_at) <= ::Presence::MAX_BACKLOG_AGE_SECONDS
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
module ::Presence
|
|
MAX_BACKLOG_AGE_SECONDS = 10
|
|
|
|
class Engine < ::Rails::Engine
|
|
engine_name PLUGIN_NAME
|
|
isolate_namespace Presence
|
|
end
|
|
end
|
|
|
|
require_dependency "application_controller"
|
|
|
|
class Presence::PresencesController < ::ApplicationController
|
|
requires_plugin PLUGIN_NAME
|
|
before_action :ensure_logged_in
|
|
before_action :ensure_presence_enabled
|
|
|
|
EDITING_STATE = 'editing'
|
|
REPLYING_STATE = 'replying'
|
|
CLOSED_STATE = 'closed'
|
|
|
|
def handle_message
|
|
[:state, :topic_id].each do |key|
|
|
raise ActionController::ParameterMissing.new(key) unless params.key?(key)
|
|
end
|
|
|
|
topic_id = permitted_params[:topic_id]
|
|
topic = Topic.find_by(id: topic_id)
|
|
|
|
raise Discourse::InvalidParameters.new(:topic_id) unless topic
|
|
guardian.ensure_can_see!(topic)
|
|
|
|
post = nil
|
|
|
|
if (permitted_params[:post_id])
|
|
if (permitted_params[:state] != EDITING_STATE)
|
|
raise Discourse::InvalidParameters.new(:state)
|
|
end
|
|
|
|
post = Post.find_by(id: permitted_params[:post_id])
|
|
raise Discourse::InvalidParameters.new(:topic_id) unless post
|
|
|
|
guardian.ensure_can_edit!(post)
|
|
end
|
|
|
|
opts = {
|
|
max_backlog_age: Presence::MAX_BACKLOG_AGE_SECONDS
|
|
}
|
|
|
|
case permitted_params[:state]
|
|
when EDITING_STATE
|
|
opts[:group_ids] = [Group::AUTO_GROUPS[:staff]]
|
|
|
|
if !post.locked? && !permitted_params[:is_whisper]
|
|
opts[:user_ids] = [post.user_id]
|
|
|
|
if topic.private_message?
|
|
if post.wiki
|
|
opts[:user_ids] = opts[:user_ids].concat(
|
|
topic.allowed_users.where(
|
|
"trust_level >= ? AND NOT admin OR moderator",
|
|
SiteSetting.min_trust_to_edit_wiki_post
|
|
).pluck(:id)
|
|
)
|
|
|
|
opts[:user_ids].uniq!
|
|
|
|
# Ignore trust level and just publish to all allowed groups since
|
|
# trying to figure out which users in the allowed groups have
|
|
# the necessary trust levels can lead to a large array of user ids
|
|
# if the groups are big.
|
|
opts[:group_ids] = opts[:group_ids].concat(
|
|
topic.allowed_groups.pluck(:id)
|
|
)
|
|
end
|
|
else
|
|
if post.wiki
|
|
opts[:group_ids] << Group::AUTO_GROUPS[:"trust_level_#{SiteSetting.min_trust_to_edit_wiki_post}"]
|
|
elsif SiteSetting.trusted_users_can_edit_others?
|
|
opts[:group_ids] << Group::AUTO_GROUPS[:trust_level_4]
|
|
end
|
|
end
|
|
end
|
|
when REPLYING_STATE
|
|
if permitted_params[:is_whisper]
|
|
opts[:group_ids] = [Group::AUTO_GROUPS[:staff]]
|
|
elsif topic.private_message?
|
|
opts[:user_ids] = topic.allowed_users.pluck(:id)
|
|
|
|
opts[:group_ids] = [Group::AUTO_GROUPS[:staff]].concat(
|
|
topic.allowed_groups.pluck(:id)
|
|
)
|
|
else
|
|
opts[:group_ids] = topic.secure_group_ids
|
|
end
|
|
when CLOSED_STATE
|
|
if topic.private_message?
|
|
opts[:user_ids] = topic.allowed_users.pluck(:id)
|
|
|
|
opts[:group_ids] = [Group::AUTO_GROUPS[:staff]].concat(
|
|
topic.allowed_groups.pluck(:id)
|
|
)
|
|
else
|
|
opts[:group_ids] = topic.secure_group_ids
|
|
end
|
|
end
|
|
|
|
payload = {
|
|
user: BasicUserSerializer.new(current_user, root: false).as_json,
|
|
state: permitted_params[:state],
|
|
is_whisper: permitted_params[:is_whisper].present?,
|
|
published_at: Time.zone.now.to_i
|
|
}
|
|
|
|
if (post_id = permitted_params[:post_id]).present?
|
|
payload[:post_id] = post_id
|
|
end
|
|
|
|
MessageBus.publish("/presence/#{topic_id}", payload, opts)
|
|
|
|
render json: success_json
|
|
end
|
|
|
|
private
|
|
|
|
def ensure_presence_enabled
|
|
if !SiteSetting.presence_enabled ||
|
|
current_user.user_option.hide_profile_and_presence?
|
|
|
|
raise Discourse::NotFound
|
|
end
|
|
end
|
|
|
|
def permitted_params
|
|
params.permit(:state, :topic_id, :post_id, :is_whisper)
|
|
end
|
|
end
|
|
|
|
Presence::Engine.routes.draw do
|
|
post '/publish' => 'presences#handle_message'
|
|
end
|
|
|
|
Discourse::Application.routes.append do
|
|
mount ::Presence::Engine, at: '/presence'
|
|
end
|
|
|
|
end
|