mirror of
https://github.com/discourse/discourse.git
synced 2024-11-30 19:05:18 +08:00
30990006a9
This reduces chances of errors where consumers of strings mutate inputs and reduces memory usage of the app. Test suite passes now, but there may be some stuff left, so we will run a few sites on a branch prior to merging
180 lines
5.0 KiB
Ruby
180 lines
5.0 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
# name: discourse-presence
|
|
# about: Show which users are writing a reply to a topic
|
|
# version: 1.0
|
|
# authors: André Pereira, David Taylor
|
|
# 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
|
|
|
|
module ::Presence
|
|
class Engine < ::Rails::Engine
|
|
engine_name PLUGIN_NAME
|
|
isolate_namespace Presence
|
|
end
|
|
end
|
|
|
|
module ::Presence::PresenceManager
|
|
MAX_BACKLOG_AGE ||= 60
|
|
|
|
def self.get_redis_key(type, id)
|
|
"presence:#{type}:#{id}"
|
|
end
|
|
|
|
def self.get_messagebus_channel(type, id)
|
|
"/presence/#{type}/#{id}"
|
|
end
|
|
|
|
# return true if a key was added
|
|
def self.add(type, id, user_id)
|
|
key = get_redis_key(type, id)
|
|
result = $redis.hset(key, user_id, Time.zone.now)
|
|
$redis.expire(key, MAX_BACKLOG_AGE)
|
|
result
|
|
end
|
|
|
|
# return true if a key was deleted
|
|
def self.remove(type, id, user_id)
|
|
key = get_redis_key(type, id)
|
|
$redis.expire(key, MAX_BACKLOG_AGE)
|
|
$redis.hdel(key, user_id) > 0
|
|
end
|
|
|
|
def self.get_users(type, id)
|
|
user_ids = $redis.hkeys(get_redis_key(type, id)).map(&:to_i)
|
|
User.where(id: user_ids)
|
|
end
|
|
|
|
def self.publish(type, id)
|
|
users = get_users(type, id)
|
|
serialized_users = users.map { |u| BasicUserSerializer.new(u, root: false) }
|
|
message = { users: serialized_users, time: Time.now.to_i }
|
|
messagebus_channel = get_messagebus_channel(type, id)
|
|
|
|
topic = type == 'post' ? Post.find_by(id: id).topic : Topic.find_by(id: id)
|
|
|
|
if topic.private_message?
|
|
user_ids = User.where('admin OR moderator').pluck(:id) + topic.allowed_users.pluck(:id)
|
|
group_ids = topic.allowed_groups.pluck(:id)
|
|
|
|
MessageBus.publish(
|
|
messagebus_channel,
|
|
message.as_json,
|
|
user_ids: user_ids,
|
|
group_ids: group_ids,
|
|
max_backlog_age: MAX_BACKLOG_AGE
|
|
)
|
|
else
|
|
MessageBus.publish(
|
|
messagebus_channel,
|
|
message.as_json,
|
|
group_ids: topic.secure_group_ids,
|
|
max_backlog_age: MAX_BACKLOG_AGE
|
|
)
|
|
end
|
|
|
|
users
|
|
end
|
|
|
|
def self.cleanup(type, id)
|
|
has_changed = false
|
|
|
|
# Delete entries older than 20 seconds
|
|
hash = $redis.hgetall(get_redis_key(type, id))
|
|
hash.each do |user_id, time|
|
|
if Time.zone.now - Time.parse(time) >= 20
|
|
has_changed |= remove(type, id, user_id)
|
|
end
|
|
end
|
|
|
|
has_changed
|
|
end
|
|
|
|
end
|
|
|
|
require_dependency "application_controller"
|
|
|
|
class Presence::PresencesController < ::ApplicationController
|
|
requires_plugin PLUGIN_NAME
|
|
before_action :ensure_logged_in
|
|
|
|
ACTIONS ||= [-"edit", -"reply"].freeze
|
|
|
|
def publish
|
|
raise Discourse::NotFound if current_user.blank? || current_user.user_option.hide_profile_and_presence?
|
|
|
|
data = params.permit(
|
|
:response_needed,
|
|
current: [:action, :topic_id, :post_id],
|
|
previous: [:action, :topic_id, :post_id]
|
|
)
|
|
|
|
payload = {}
|
|
|
|
if data[:previous] && data[:previous][:action].in?(ACTIONS)
|
|
type = data[:previous][:post_id] ? 'post' : 'topic'
|
|
id = data[:previous][:post_id] ? data[:previous][:post_id] : data[:previous][:topic_id]
|
|
|
|
topic = type == 'post' ? Post.find_by(id: id)&.topic : Topic.find_by(id: id)
|
|
|
|
if topic
|
|
guardian.ensure_can_see!(topic)
|
|
|
|
Presence::PresenceManager.remove(type, id, current_user.id)
|
|
Presence::PresenceManager.cleanup(type, id)
|
|
Presence::PresenceManager.publish(type, id)
|
|
end
|
|
end
|
|
|
|
if data[:current] && data[:current][:action].in?(ACTIONS)
|
|
type = data[:current][:post_id] ? 'post' : 'topic'
|
|
id = data[:current][:post_id] ? data[:current][:post_id] : data[:current][:topic_id]
|
|
|
|
topic = type == 'post' ? Post.find_by(id: id)&.topic : Topic.find_by(id: id)
|
|
|
|
if topic
|
|
guardian.ensure_can_see!(topic)
|
|
|
|
Presence::PresenceManager.add(type, id, current_user.id)
|
|
Presence::PresenceManager.cleanup(type, id)
|
|
users = Presence::PresenceManager.publish(type, id)
|
|
|
|
if data[:response_needed]
|
|
messagebus_channel = Presence::PresenceManager.get_messagebus_channel(type, id)
|
|
users ||= Presence::PresenceManager.get_users(type, id)
|
|
payload = json_payload(messagebus_channel, users)
|
|
end
|
|
end
|
|
end
|
|
|
|
render json: payload
|
|
end
|
|
|
|
def json_payload(channel, users)
|
|
{
|
|
messagebus_channel: channel,
|
|
messagebus_id: MessageBus.last_id(channel),
|
|
users: users.limit(SiteSetting.presence_max_users_shown).map { |u| BasicUserSerializer.new(u, root: false) }
|
|
}
|
|
end
|
|
|
|
end
|
|
|
|
Presence::Engine.routes.draw do
|
|
post '/publish' => 'presences#publish'
|
|
end
|
|
|
|
Discourse::Application.routes.append do
|
|
mount ::Presence::Engine, at: '/presence'
|
|
end
|
|
|
|
end
|