feat(broadcasting): add ActionCable adapter + fix model broadcast anti-pattern
- Add Mozo::Broadcaster::ActionCable as drop-in Faye replacement - Fix model_broadcast.rb: delegate to Mozo directly instead of ApplicationController.new (memory-unsafe anti-pattern) - Add Broadcastable concern for clean model-side broadcasting - ActionCable config: async adapter, cable.yml, WebSocket endpoint - MozoChannel with per-entity authorization (user/supplier/employee) - Connection auth via auth_token (matches existing auth pattern) - Mount /cable WebSocket in routes - Add broadcasting-migration.md with Faye→ActionCable guide
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
# Authenticate via auth_token (same mechanism used in ApplicationController#authenticate_employee!)
|
||||
# Clients should pass ?auth_token=TOKEN when connecting to the WebSocket.
|
||||
identified_by :current_user, :current_entity_type
|
||||
|
||||
def connect
|
||||
token = request.params[:auth_token].presence
|
||||
reject_unauthorized_connection unless token
|
||||
|
||||
if (employee = Employee.find_by_authentication_token(token))
|
||||
self.current_user = employee
|
||||
self.current_entity_type = :employee
|
||||
elsif (user = User.find_by_authentication_token(token))
|
||||
self.current_user = user
|
||||
self.current_entity_type = :user
|
||||
elsif (supplier = Supplier.find_by_authentication_token(token))
|
||||
self.current_user = supplier
|
||||
self.current_entity_type = :supplier
|
||||
else
|
||||
reject_unauthorized_connection
|
||||
end
|
||||
end
|
||||
|
||||
# Allow subscribing to the entity's own channel
|
||||
def subscribe_to_self
|
||||
case current_entity_type
|
||||
when :user then "user_#{current_user.id}"
|
||||
when :supplier then "supplier_#{current_user.id}"
|
||||
when :employee then "employee_#{current_user.id}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Base channel. Streams are set up dynamically by clients subscribing
|
||||
# to their entity channel (user_123, supplier_456, etc.).
|
||||
#
|
||||
# The server broadcasts TO these channels via:
|
||||
# ActionCable.server.broadcast("user_123", { event: "...", data: {...} })
|
||||
#
|
||||
# Clients connect and subscribe via:
|
||||
# consumer.subscriptions.create({ channel: "MozoChannel", id: "user_123" })
|
||||
#
|
||||
class MozoChannel < ApplicationCable::Channel
|
||||
def subscribed
|
||||
stream_name = params[:id]
|
||||
if authorized?(stream_name)
|
||||
stream_from stream_name
|
||||
else
|
||||
reject
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
# cleanup
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorized?(stream_name)
|
||||
prefix, id = stream_name.to_s.split('_', 2)
|
||||
case prefix
|
||||
when 'user'
|
||||
connection.current_entity_type == :user && connection.current_user.id.to_s == id
|
||||
when 'supplier'
|
||||
connection.current_entity_type == :supplier && connection.current_user.id.to_s == id
|
||||
when 'employee'
|
||||
connection.current_entity_type == :employee && connection.current_user.id.to_s == id
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Include this in any model that needs to broadcast events to users/suppliers.
|
||||
#
|
||||
# Replaces the old model_broadcast.rb initializer which monkey-patched
|
||||
# SimplyStored::Couch and created ApplicationController.new per broadcast
|
||||
# (memory-unsafe, no request context, to be removed once all callers migrate).
|
||||
#
|
||||
# Usage:
|
||||
# class List < ApplicationRecord
|
||||
# include Broadcastable
|
||||
#
|
||||
# def close!
|
||||
# broadcast_user(user.id, 'list_closed', { id: id })
|
||||
# broadcast_supplier(supplier_id, 'list_closed', { id: id })
|
||||
# end
|
||||
# end
|
||||
#
|
||||
module Broadcastable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def broadcast_supplier(sid, event, data = {})
|
||||
Mozo.broadcast_supplier(sid, event, data)
|
||||
end
|
||||
|
||||
def broadcast_user(uid, event, data = {})
|
||||
Mozo.broadcast_user(uid, event, data)
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user