chore: clean up

This commit is contained in:
Sojan Jose
2025-07-17 04:53:37 -07:00
parent f8a8679d88
commit 669241801e
15 changed files with 303 additions and 338 deletions

View File

@@ -1,19 +1,19 @@
class Api::V1::Accounts::Channels::Voice::WebhooksController < Api::V1::Accounts::BaseController
skip_before_action :authenticate_user!, :set_current_user, only: [:incoming, :conference_status]
protect_from_forgery with: :null_session, only: [:incoming, :conference_status]
before_action :validate_twilio_signature, only: [:incoming]
before_action :handle_options_request, only: [:incoming, :conference_status]
skip_before_action :authenticate_user!, :set_current_user, only: [:incoming, :conference_status, :call_status]
protect_from_forgery with: :null_session, only: [:incoming, :conference_status, :call_status]
before_action :validate_twilio_signature, only: [:incoming, :call_status]
before_action :handle_options_request, only: [:incoming, :conference_status, :call_status]
# Handle CORS preflight OPTIONS requests
def handle_options_request
if request.method == "OPTIONS"
if request.method == 'OPTIONS'
set_cors_headers
head :ok
return true
end
false
end
def set_cors_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, OPTIONS'
@@ -25,78 +25,121 @@ class Api::V1::Accounts::Channels::Voice::WebhooksController < Api::V1::Accounts
def incoming
# Set CORS headers first to ensure they're included
set_cors_headers
# Log basic request info
Rails.logger.info("🔔 INCOMING CALL WEBHOOK: CallSid=#{params['CallSid']} From=#{params['From']} To=#{params['To']}")
# Process incoming call using service
begin
# Ensure account is set properly
if !Current.account && params[:account_id].present?
Current.account = Account.find(params[:account_id])
Rails.logger.info("👑 Set Current.account to #{Current.account.id}")
end
Current.account = Account.find(params[:account_id]) if !Current.account && params[:account_id].present?
# Validate required parameters
validate_incoming_params
# Process the call
service = Voice::IncomingCallService.new(
account: Current.account,
account: Current.account,
params: params.to_unsafe_h.merge(host_with_port: request.host_with_port)
)
twiml_response = service.process
# Return TwiML response
Rails.logger.info("✅ INCOMING CALL: Successfully processed")
render xml: twiml_response
rescue StandardError => e
# Log the error with detailed information
Rails.logger.error("❌ INCOMING CALL ERROR: #{e.message}")
Rails.logger.error("❌ BACKTRACE: #{e.backtrace[0..5].join("\n")}")
Rails.logger.error("Incoming call error: #{e.message}")
# Return friendly error message to caller
render_error("We're sorry, but we're experiencing technical difficulties. Please try your call again later.")
end
end
# Handle individual call status updates
def call_status
# Set CORS headers first to ensure they're always included
set_cors_headers
# Return immediately for OPTIONS requests
return head :ok if request.method == 'OPTIONS'
# Process call status updates
begin
# Set account for local development if needed
Current.account = Account.find(params[:account_id]) if !Current.account && params[:account_id].present?
# Find conversation by CallSid
call_sid = params['CallSid']
# For dial action callbacks, use DialCallStatus; fallback to CallStatus for other types
call_status = params['DialCallStatus'] || params['CallStatus']
if call_sid.present? && call_status.present?
conversation = Current.account.conversations.where("additional_attributes->>'call_sid' = ?", call_sid).first
if conversation
# Use CallStatusManager to handle the status update
status_manager = Voice::CallStatus::Manager.new(
conversation: conversation,
call_sid: call_sid,
provider: :twilio
)
# Map Twilio call/dial statuses to our statuses and update
case call_status.downcase
when 'completed', 'busy', 'failed', 'no-answer', 'canceled'
# Standard call status values
if conversation.additional_attributes['call_status'] == 'ringing'
status_manager.process_status_update('no_answer')
else
status_manager.process_status_update('ended')
end
when 'answered'
# DialCallStatus: conference calls return 'answered' when successful
# No action needed - call continues in conference
else
# Handle any other dial statuses (busy, no-answer, failed from dial action)
if conversation.additional_attributes['call_status'] == 'ringing'
status_manager.process_status_update('no_answer')
else
status_manager.process_status_update('ended')
end
end
end
end
# Call status processed successfully
rescue StandardError => e
# Log errors but don't affect the response
Rails.logger.error("Call status error: #{e.message}")
end
# Always return a successful response for Twilio
head :ok
end
# Handle conference status updates
def conference_status
# Set CORS headers first to ensure they're always included
set_cors_headers
# Return immediately for OPTIONS requests
if request.method == "OPTIONS"
return head :ok
end
# Log basic request info
Rails.logger.info("🎧 CONFERENCE STATUS WEBHOOK: ConferenceSid=#{params['ConferenceSid']} Event=#{params['StatusCallbackEvent']}")
return head :ok if request.method == 'OPTIONS'
# Process conference status updates using service
begin
# Set account for local development if needed
if !Current.account && params[:account_id].present?
Current.account = Account.find(params[:account_id])
Rails.logger.info("👑 Set Current.account to #{Current.account.id}")
end
# Validate required parameters
if params['ConferenceSid'].blank? && params['CallSid'].blank?
Rails.logger.error("❌ MISSING REQUIRED PARAMS: Need either ConferenceSid or CallSid")
end
Current.account = Account.find(params[:account_id]) if !Current.account && params[:account_id].present?
# Validate required parameters - need either ConferenceSid or CallSid
return head :ok if params['ConferenceSid'].blank? && params['CallSid'].blank?
# Use service to process conference status
service = Voice::ConferenceStatusService.new(account: Current.account, params: params)
service.process
Rails.logger.info("✅ CONFERENCE STATUS: Successfully processed")
# Conference status processed successfully
rescue StandardError => e
# Log errors but don't affect the response
Rails.logger.error("❌ CONFERENCE STATUS ERROR: #{e.message}")
Rails.logger.error("❌ BACKTRACE: #{e.backtrace[0..5].join("\n")}")
Rails.logger.error("Conference status error: #{e.message}")
end
# Always return a successful response for Twilio
head :ok
end
@@ -104,43 +147,34 @@ class Api::V1::Accounts::Channels::Voice::WebhooksController < Api::V1::Accounts
private
def validate_incoming_params
if params['CallSid'].blank?
raise "Missing required parameter: CallSid"
end
if params['From'].blank?
raise "Missing required parameter: From"
end
if params['To'].blank?
raise "Missing required parameter: To"
end
if Current.account.nil?
raise "Current account not set"
end
raise 'Missing required parameter: CallSid' if params['CallSid'].blank?
raise 'Missing required parameter: From' if params['From'].blank?
raise 'Missing required parameter: To' if params['To'].blank?
return unless Current.account.nil?
raise 'Current account not set'
end
def validate_twilio_signature
begin
validator = Voice::TwilioValidatorService.new(
account: Current.account,
params: params,
request: request
)
if !validator.valid?
Rails.logger.error("❌ INVALID TWILIO SIGNATURE")
render_error('Invalid Twilio signature')
return false
end
return true
rescue StandardError => e
Rails.logger.error("❌ TWILIO VALIDATION ERROR: #{e.message}")
render_error('Error validating Twilio request')
validator = Voice::TwilioValidatorService.new(
account: Current.account,
params: params,
request: request
)
unless validator.valid?
render_error('Invalid Twilio signature')
return false
end
return true
rescue StandardError => e
Rails.logger.error("Twilio validation error: #{e.message}")
render_error('Error validating Twilio request')
return false
end
def render_error(message)
@@ -149,4 +183,4 @@ class Api::V1::Accounts::Channels::Voice::WebhooksController < Api::V1::Accounts
response.hangup
render xml: response.to_s
end
end
end

View File

@@ -21,7 +21,6 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController
provider: :twilio)
.process_status_update('completed', nil, false, "Call ended by #{current_user.name}")
broadcast_status(call_sid, 'completed')
render_success('Call successfully ended')
rescue StandardError => e
render_error("Failed to end call: #{e.message}")
@@ -35,7 +34,6 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController
conference_sid = convo_attr('conference_sid') || create_conference_sid!
update_join_metadata!(call_sid)
broadcast_status(call_sid, 'in-progress')
render json: {
status: 'success',
@@ -158,18 +156,6 @@ class Api::V1::Accounts::VoiceController < Api::V1::Accounts::BaseController
.process_status_update('in_progress', nil, false, "#{current_user.name} joined the call")
end
def broadcast_status(call_sid, status)
ActionCable.server.broadcast "account_#{@conversation.account_id}", {
event_name: 'call_status_changed',
data: {
call_sid: call_sid,
status: status,
conversation_id: @conversation.display_id,
inbox_id: @conversation.inbox_id,
timestamp: Time.current.to_i
}
}
end
# ---- TwiML -----------------------------------------------------------------