11# frozen_string_literal: true
22
3- # DirectMessageChannel — real-time private messaging between two team members .
3+ # DirectMessageChannel — real-time private messaging between staff and players .
44#
5- # The frontend subscribes passing the recipient_id:
5+ # The frontend subscribes passing recipient_id and optionally recipient_type :
66# consumer.subscriptions.create(
7- # { channel: 'DirectMessageChannel', recipient_id: '<uuid>' },
7+ # { channel: 'DirectMessageChannel', recipient_id: '<uuid>', recipient_type: 'Player' },
88# { received(data) { ... } }
99# )
1010#
1111# Security guarantees:
12- # 1. Sender identity comes from the verified JWT (current_user) — cannot be spoofed.
12+ # 1. Sender identity comes from the verified JWT (current_user or current_player ) — cannot be spoofed.
1313# 2. Recipient must belong to the same organization as the sender.
14- # 3. Stream key is derived from sorted user IDs + org_id — impossible to subscribe
15- # to a conversation you're not a party to.
14+ # 3. Stream key is derived from sorted participant IDs + org_id — impossible to subscribe
15+ # to a conversation you are not a party to.
1616class DirectMessageChannel < ApplicationCable ::Channel
1717 MAX_CONTENT_LENGTH = 2000
1818
1919 def subscribed
2020 recipient = find_and_validate_recipient
2121 return unless recipient
2222
23- @recipient_id = recipient . id
24- stream_from stream_key_for ( recipient )
25- logger . info "[DM] #{ current_user . id } subscribed to DM with #{ recipient . id } "
23+ @recipient_id = recipient [ :record ] . id
24+ @recipient_type = recipient [ :type ]
25+ stream_from Message . dm_stream_key ( current_sender_id , @recipient_id , current_org_id )
26+ logger . info "[DM] #{ current_sender_id } subscribed to DM with #{ @recipient_id } "
2627 end
2728
2829 def unsubscribed
2930 stop_all_streams
3031 end
3132
32- # Receives { "content" => "...", "recipient_id" => "..." } from the frontend .
33+ # Receives { "content" => "...", "recipient_id" => "...", "recipient_type" => "..." } from client .
3334 def speak ( data ) # rubocop:disable Metrics/MethodLength
3435 content = data [ 'content' ] . to_s . strip
3536 recipient_id = data [ 'recipient_id' ] . to_s
@@ -47,12 +48,7 @@ def speak(data) # rubocop:disable Metrics/MethodLength
4748 recipient = find_recipient_by_id ( recipient_id )
4849 return unless recipient
4950
50- Message . create! (
51- content : content ,
52- user : current_user ,
53- recipient : recipient ,
54- organization_id : current_org_id
55- )
51+ create_message ( content : content , recipient : recipient )
5652 rescue ActiveRecord ::RecordInvalid => e
5753 logger . error "[DM] Failed to create message: #{ e . message } "
5854 transmit ( { error : 'Failed to send message' } )
@@ -73,24 +69,54 @@ def find_and_validate_recipient
7369 end
7470
7571 def find_recipient_by_id ( recipient_id )
76- recipient = User . find_by ( id : recipient_id , organization_id : current_org_id )
72+ recipient_type = resolve_recipient_type ( params [ :recipient_type ] )
73+ record = locate_recipient ( recipient_id , recipient_type )
7774
78- unless recipient
79- logger . warn "[DM] Recipient #{ recipient_id } not found in org #{ current_org_id } "
75+ unless record
76+ logger . warn "[DM] Recipient #{ recipient_id } ( #{ recipient_type } ) not found in org #{ current_org_id } "
8077 reject
8178 return nil
8279 end
8380
84- if recipient . id == current_user . id
81+ if record . id == current_sender_id
8582 logger . warn '[DM] Cannot DM yourself'
8683 reject
8784 return nil
8885 end
8986
90- recipient
87+ { record : record , type : recipient_type }
88+ end
89+
90+ def locate_recipient ( recipient_id , recipient_type )
91+ if recipient_type == 'Player'
92+ Player . find_by ( id : recipient_id , organization_id : current_org_id , player_access_enabled : true )
93+ else
94+ User . find_by ( id : recipient_id , organization_id : current_org_id )
95+ end
96+ end
97+
98+ def resolve_recipient_type ( raw_type )
99+ Message ::PARTICIPANT_TYPES . include? ( raw_type . to_s ) ? raw_type . to_s : 'User'
100+ end
101+
102+ def create_message ( content :, recipient :)
103+ Message . create! (
104+ user_id : current_sender_id ,
105+ sender_type : current_sender_type ,
106+ recipient_id : recipient [ :record ] . id ,
107+ recipient_type : recipient [ :type ] ,
108+ organization_id : current_org_id ,
109+ content : content
110+ )
111+ end
112+
113+ def current_sender_id
114+ return current_player . id if current_player . present?
115+
116+ current_user . id
91117 end
92118
93- def stream_key_for ( recipient )
94- Message . dm_stream_key ( current_user . id , recipient . id , current_org_id )
119+ def current_sender_type
120+ current_player . present? ? 'Player' : 'User'
95121 end
96122end
0 commit comments