WhatsApp/Signal/Formstack/admin updates
This commit is contained in:
parent
bcecf61a46
commit
d0cc5a21de
451 changed files with 16139 additions and 39623 deletions
|
|
@ -115,7 +115,21 @@ class ChannelsCdrSignalController < ApplicationController
|
|||
|
||||
channel = channel_for_token(token)
|
||||
return render json: {}, status: 401 if !channel || !channel.active
|
||||
return render json: {}, status: 401 if channel.options[:token] != token
|
||||
# Use constant-time comparison to prevent timing attacks
|
||||
return render json: {}, status: 401 unless ActiveSupport::SecurityUtils.secure_compare(
|
||||
channel.options[:token].to_s,
|
||||
token.to_s
|
||||
)
|
||||
|
||||
# Handle group creation events
|
||||
if params[:event] == 'group_created'
|
||||
return update_group
|
||||
end
|
||||
|
||||
# Handle group member joined events
|
||||
if params[:event] == 'group_member_joined'
|
||||
return handle_group_member_joined
|
||||
end
|
||||
|
||||
channel_id = channel.id
|
||||
|
||||
|
|
@ -141,6 +155,13 @@ class ChannelsCdrSignalController < ApplicationController
|
|||
|
||||
receiver_phone_number = params[:to].strip
|
||||
sender_phone_number = params[:from].strip
|
||||
|
||||
# Check if this is a group message using the is_group flag from bridge-worker
|
||||
# This flag is set when:
|
||||
# 1. The original message came from a Signal group
|
||||
# 2. Bridge-worker created a new group for the conversation
|
||||
is_group_message = params[:is_group].to_s == 'true' || params[:is_group].to_s == 'true'
|
||||
|
||||
customer = User.find_by(phone: sender_phone_number)
|
||||
customer ||= User.find_by(mobile: sender_phone_number)
|
||||
unless customer
|
||||
|
|
@ -192,23 +213,69 @@ class ChannelsCdrSignalController < ApplicationController
|
|||
|
||||
# find ticket or create one
|
||||
state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
|
||||
ticket = Ticket.where(customer_id: customer.id).where.not(state_id: state_ids).order(:updated_at).first
|
||||
|
||||
if is_group_message
|
||||
Rails.logger.info "=== SIGNAL GROUP TICKET LOOKUP ==="
|
||||
Rails.logger.info "Looking for ticket with group_id: #{receiver_phone_number}"
|
||||
Rails.logger.info "Customer ID: #{customer.id}"
|
||||
Rails.logger.info "Customer Phone: #{sender_phone_number}"
|
||||
Rails.logger.info "Channel ID: #{channel.id}"
|
||||
|
||||
begin
|
||||
# Use text search on preferences YAML to efficiently find tickets without loading all into memory
|
||||
# This prevents DoS attacks from memory exhaustion
|
||||
ticket = Ticket.where.not(state_id: state_ids)
|
||||
.where("preferences LIKE ?", "%channel_id: #{channel.id}%")
|
||||
.where("preferences LIKE ?", "%chat_id: #{receiver_phone_number}%")
|
||||
.order(updated_at: :desc)
|
||||
.first
|
||||
|
||||
if ticket
|
||||
Rails.logger.info "=== FOUND MATCHING TICKET BY GROUP ID: ##{ticket.number} ==="
|
||||
# Update customer if different (handles duplicate phone numbers)
|
||||
if ticket.customer_id != customer.id
|
||||
Rails.logger.info "Updating ticket customer from #{ticket.customer_id} to #{customer.id}"
|
||||
ticket.customer_id = customer.id
|
||||
end
|
||||
else
|
||||
Rails.logger.info "=== NO MATCHING TICKET BY GROUP ID - CHECKING BY PHONE NUMBER ==="
|
||||
end
|
||||
rescue => e
|
||||
Rails.logger.error "Error during group ticket lookup: #{e.message}"
|
||||
Rails.logger.error e.backtrace.join("\n")
|
||||
end
|
||||
else
|
||||
Rails.logger.info "Not a group message or no group_id, finding most recent ticket"
|
||||
ticket = Ticket.where(customer_id: customer.id).where.not(state_id: state_ids).order(:updated_at).first
|
||||
end
|
||||
|
||||
if ticket
|
||||
# check if title need to be updated
|
||||
ticket.title = title if ticket.title == '-'
|
||||
new_state = Ticket::State.find_by(default_create: true)
|
||||
ticket.state = Ticket::State.find_by(default_follow_up: true) if ticket.state_id != new_state.id
|
||||
else
|
||||
# Set up chat_id based on whether this is a group message
|
||||
chat_id = is_group_message ? receiver_phone_number : sender_phone_number
|
||||
|
||||
# Build preferences with group_id included if needed
|
||||
cdr_signal_prefs = {
|
||||
bot_token: channel.options[:bot_token], # change to bot id
|
||||
chat_id: chat_id
|
||||
}
|
||||
|
||||
Rails.logger.info "=== CREATING NEW TICKET ==="
|
||||
Rails.logger.info "Preferences to be stored:"
|
||||
Rails.logger.info " - channel_id: #{channel.id}"
|
||||
Rails.logger.info " - cdr_signal: #{cdr_signal_prefs.inspect}"
|
||||
|
||||
ticket = Ticket.new(
|
||||
group_id: channel.group_id,
|
||||
title: title,
|
||||
customer_id: customer.id,
|
||||
preferences: {
|
||||
channel_id: channel.id,
|
||||
cdr_signal: {
|
||||
bot_token: channel.options[:bot_token], # change to bot id
|
||||
chat_id: sender_phone_number
|
||||
}
|
||||
cdr_signal: cdr_signal_prefs
|
||||
}
|
||||
)
|
||||
end
|
||||
|
|
@ -224,7 +291,7 @@ class ChannelsCdrSignalController < ApplicationController
|
|||
content_type: 'text/plain',
|
||||
message_id: "cdr_signal.#{message_id}",
|
||||
ticket_id: ticket.id,
|
||||
internal: false,
|
||||
internal: params[:internal] == true,
|
||||
preferences: {
|
||||
cdr_signal: {
|
||||
timestamp: sent_at,
|
||||
|
|
@ -265,4 +332,137 @@ class ChannelsCdrSignalController < ApplicationController
|
|||
|
||||
render json: result, status: :ok
|
||||
end
|
||||
|
||||
# Webhook endpoint for receiving group creation notifications from bridge-worker
|
||||
# This is called when a Signal group is created for a conversation
|
||||
# Expected payload:
|
||||
# {
|
||||
# "event": "group_created",
|
||||
# "conversation_id": "ticket_id_or_number",
|
||||
# "original_recipient": "+1234567890",
|
||||
# "group_id": "uuid-of-signal-group",
|
||||
# "timestamp": "ISO8601 timestamp"
|
||||
# }
|
||||
def update_group
|
||||
# Validate required parameters
|
||||
errors = {}
|
||||
errors['event'] = 'required' unless params[:event].present?
|
||||
errors['conversation_id'] = 'required' unless params[:conversation_id].present?
|
||||
errors['group_id'] = 'required' unless params[:group_id].present?
|
||||
|
||||
if errors.present?
|
||||
render json: {
|
||||
errors: errors
|
||||
}, status: :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
# Only handle group_created events for now
|
||||
unless params[:event] == 'group_created'
|
||||
render json: { error: 'Unsupported event type' }, status: :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
# Find the ticket by ID or number
|
||||
# Try to find by both ID and number since ticket numbers can be numeric
|
||||
ticket = Ticket.find_by(id: params[:conversation_id]) ||
|
||||
Ticket.find_by(number: params[:conversation_id])
|
||||
|
||||
unless ticket
|
||||
Rails.logger.error "Signal group update: Ticket not found for conversation_id #{params[:conversation_id]}"
|
||||
render json: { error: 'Ticket not found' }, status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
# Update ticket preferences with the group information
|
||||
ticket.preferences ||= {}
|
||||
ticket.preferences[:cdr_signal] ||= {}
|
||||
ticket.preferences[:cdr_signal][:chat_id] = params[:group_id]
|
||||
ticket.preferences[:cdr_signal][:original_recipient] = params[:original_recipient] if params[:original_recipient].present?
|
||||
ticket.preferences[:cdr_signal][:group_created_at] = params[:timestamp] if params[:timestamp].present?
|
||||
|
||||
# Track whether user has joined the group (initially false)
|
||||
# This will be updated to true when we receive a group join event from Signal
|
||||
ticket.preferences[:cdr_signal][:group_joined] = params[:group_joined] if params.key?(:group_joined)
|
||||
|
||||
ticket.save!
|
||||
|
||||
Rails.logger.info "Signal group #{params[:group_id]} associated with ticket #{ticket.id}"
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
ticket_id: ticket.id,
|
||||
ticket_number: ticket.number
|
||||
}, status: :ok
|
||||
end
|
||||
|
||||
# Webhook endpoint for receiving group member joined notifications from bridge-worker
|
||||
# This is called when a user accepts the Signal group invitation
|
||||
# Expected payload:
|
||||
# {
|
||||
# "event": "group_member_joined",
|
||||
# "group_id": "group.base64encodedid",
|
||||
# "member_phone": "+1234567890",
|
||||
# "timestamp": "ISO8601 timestamp"
|
||||
# }
|
||||
def handle_group_member_joined
|
||||
# Validate required parameters
|
||||
errors = {}
|
||||
errors['event'] = 'required' unless params[:event].present?
|
||||
errors['group_id'] = 'required' unless params[:group_id].present?
|
||||
errors['member_phone'] = 'required' unless params[:member_phone].present?
|
||||
|
||||
if errors.present?
|
||||
render json: {
|
||||
errors: errors
|
||||
}, status: :bad_request
|
||||
return
|
||||
end
|
||||
|
||||
# Find ticket(s) with this group_id in preferences
|
||||
# Use text search on preferences YAML for efficient lookup (prevents DoS from loading all tickets)
|
||||
state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
|
||||
|
||||
ticket = Ticket.where.not(state_id: state_ids)
|
||||
.where("preferences LIKE ?", "%chat_id: #{params[:group_id]}%")
|
||||
.order(updated_at: :desc)
|
||||
.first
|
||||
|
||||
unless ticket
|
||||
Rails.logger.warn "Signal group member joined: Ticket not found for group_id #{params[:group_id]}"
|
||||
render json: { error: 'Ticket not found for this group' }, status: :not_found
|
||||
return
|
||||
end
|
||||
|
||||
# Idempotency check: if already marked as joined, skip update and return success
|
||||
# This prevents unnecessary database writes when the cron job sends duplicate notifications
|
||||
if ticket.preferences.dig('cdr_signal', 'group_joined') == true
|
||||
Rails.logger.debug "Signal group member #{params[:member_phone]} already marked as joined for group #{params[:group_id]} ticket #{ticket.id}, skipping update"
|
||||
render json: {
|
||||
success: true,
|
||||
ticket_id: ticket.id,
|
||||
ticket_number: ticket.number,
|
||||
group_joined: true,
|
||||
already_joined: true
|
||||
}, status: :ok
|
||||
return
|
||||
end
|
||||
|
||||
# Update group_joined flag
|
||||
member_phone = params[:member_phone]
|
||||
ticket.preferences[:cdr_signal][:group_joined] = true
|
||||
ticket.preferences[:cdr_signal][:group_joined_at] = params[:timestamp] if params[:timestamp].present?
|
||||
ticket.preferences[:cdr_signal][:group_joined_by] = member_phone
|
||||
|
||||
ticket.save!
|
||||
|
||||
Rails.logger.info "Signal group member #{member_phone} joined group #{params[:group_id]} for ticket #{ticket.id}"
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
ticket_id: ticket.id,
|
||||
ticket_number: ticket.number,
|
||||
group_joined: true
|
||||
}, status: :ok
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue