Repo cleanup

This commit is contained in:
Darren Clarke 2026-02-10 08:36:04 +01:00
parent 59872f579a
commit e941353b64
444 changed files with 1485 additions and 21978 deletions

View file

@ -0,0 +1,55 @@
# frozen_string_literal: true
class Channel
class Driver
class CdrSignal
def fetchable?(_channel)
false
end
def disconnect; end
#
# instance = Channel::Driver::CdrSignal.new
# instance.send(
# {
# adapter: 'cdrsignal',
# auth: {
# api_key: api_key
# },
# },
# signal_attributes,
# notification
# )
#
def deliver(options, article, _notification = false)
# return if we run import mode
return if Setting.get('import_mode')
options = check_external_credential(options)
Rails.logger.debug { 'signal send started' }
Rails.logger.debug { options.inspect }
@signal = ::CdrSignal.new(options[:bot_endpoint], options[:bot_token])
@signal.from_article(article)
end
def self.streamable?
false
end
private
def check_external_credential(options)
if options[:auth] && options[:auth][:external_credential_id]
external_credential = ExternalCredential.find_by(id: options[:auth][:external_credential_id])
raise "No such ExternalCredential.find(#{options[:auth][:external_credential_id]})" unless external_credential
options[:auth][:api_key] = external_credential.credentials['api_key']
end
options
end
end
end
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
class Channel::Driver::CdrWhatsapp
def fetchable?(_channel)
false
end
def disconnect; end
#
# instance = Channel::Driver::CdrWhatsapp.new
# instance.send(
# {
# adapter: 'cdr_whatsapp',
# auth: {
# api_key: api_key
# },
# },
# whatsapp_attributes,
# notification
# )
#
def deliver(options, article, _notification = false)
# return if we run import mode
return if Setting.get('import_mode')
options = check_external_credential(options)
Rails.logger.debug { 'whatsapp send started' }
Rails.logger.debug { options.inspect }
@whatsapp = ::CdrWhatsapp.new(options[:bot_endpoint], options[:bot_token])
@whatsapp.from_article(article)
end
def self.streamable?
false
end
private
def check_external_credential(options)
if options[:auth] && options[:auth][:external_credential_id]
external_credential = ExternalCredential.find_by(id: options[:auth][:external_credential_id])
raise "No such ExternalCredential.find(#{options[:auth][:external_credential_id]})" unless external_credential
options[:auth][:api_key] = external_credential.credentials['api_key']
end
options
end
end

View file

@ -0,0 +1,51 @@
# frozen_string_literal: true
module Link::SetupSplitSignalGroup
extend ActiveSupport::Concern
included do
after_create :setup_signal_group_for_split_ticket
end
private
def setup_signal_group_for_split_ticket
# Only if auto-groups enabled
return unless ENV['BRIDGE_SIGNAL_AUTO_GROUPS'].to_s.downcase == 'true'
# Only child links (splits create child->parent links)
return unless link_type_id == Link::Type.find_by(name: 'child')&.id
# Only Ticket-to-Ticket links
ticket_object_id = Link::Object.find_by(name: 'Ticket')&.id
return unless link_object_source_id == ticket_object_id
return unless link_object_target_id == ticket_object_id
child_ticket = Ticket.find_by(id: link_object_source_value)
parent_ticket = Ticket.find_by(id: link_object_target_value)
return unless child_ticket && parent_ticket
# Only if parent has Signal group (chat_id starts with "group.")
parent_signal_prefs = parent_ticket.preferences&.dig('cdr_signal')
return unless parent_signal_prefs.present?
return unless parent_signal_prefs['chat_id']&.start_with?('group.')
original_recipient = parent_signal_prefs['original_recipient']
return unless original_recipient.present?
# Set up child for lazy group creation:
# chat_id = phone number triggers new group on first message
child_ticket.preferences ||= {}
child_ticket.preferences['channel_id'] = parent_ticket.preferences['channel_id']
child_ticket.preferences['cdr_signal'] = {
'bot_token' => parent_signal_prefs['bot_token'],
'chat_id' => original_recipient, # Phone number, NOT group ID
'original_recipient' => original_recipient
}
# Set article type so Zammad shows Signal reply option
child_ticket.create_article_type_id = Ticket::Article::Type.find_by(name: 'cdr_signal')&.id
child_ticket.save!
Rails.logger.info "Signal split: Ticket ##{child_ticket.number} set up for new group (recipient: #{original_recipient})"
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Ticket::Article::EnqueueCommunicateCdrSignalJob
extend ActiveSupport::Concern
included do
after_create :ticket_article_enqueue_communicate_cdr_signal_job
end
private
def ticket_article_enqueue_communicate_cdr_signal_job
# return if we run import mode
return true if Setting.get('import_mode')
# if sender is customer, do not communicate
return true unless sender_id
sender = Ticket::Article::Sender.lookup(id: sender_id)
return true if sender.nil?
return true if sender.name == 'Customer'
# only apply on cdr signal messages
return true unless type_id
type = Ticket::Article::Type.lookup(id: type_id)
return true unless type.name.match?(/\Acdr_signal/i)
CommunicateCdrSignalJob.perform_later(id)
end
end

View file

@ -0,0 +1,32 @@
# frozen_string_literal: true
module Ticket::Article::EnqueueCommunicateCdrWhatsappJob
extend ActiveSupport::Concern
included do
after_create :ticket_article_enqueue_communicate_cdr_whatsapp_job
end
private
def ticket_article_enqueue_communicate_cdr_whatsapp_job
# return if we run import mode
return true if Setting.get('import_mode')
# if sender is customer, do not communicate
return true unless sender_id
sender = Ticket::Article::Sender.lookup(id: sender_id)
return true if sender.nil?
return true if sender.name == 'Customer'
# only apply on cdr whatsapp messages
return true unless type_id
type = Ticket::Article::Type.lookup(id: type_id)
return true unless type.name.match?(/\Acdr_whatsapp/i)
CommunicateCdrWhatsappJob.perform_later(id)
end
end

View file

@ -0,0 +1,143 @@
# frozen_string_literal: true
class Transaction::SignalNotification
include ChecksHumanChanges
def initialize(item, params = {})
@item = item
@params = params
end
def perform
return if Setting.get('import_mode')
return if %w[Ticket Ticket::Article].exclude?(@item[:object])
return if @params[:disable_notification]
return if !ticket
return if !signal_notifications_enabled?
return if !signal_channel
collect_signal_recipients.each do |user|
SignalNotificationJob.perform_later(
ticket_id: ticket.id,
article_id: @item[:article_id],
user_id: user.id,
type: @item[:type],
changes: human_changes(@item[:changes], ticket, user)
)
end
end
private
def ticket
@ticket ||= Ticket.find_by(id: @item[:object_id])
end
def article
return if !@item[:article_id]
@article ||= begin
art = Ticket::Article.find_by(id: @item[:article_id])
return unless art
sender = Ticket::Article::Sender.lookup(id: art.sender_id)
if sender&.name == 'System'
return if @item[:changes].blank? && art.preferences[:notification] != true
return if art.preferences[:notification] != true
end
art
end
end
def current_user
@current_user ||= User.lookup(id: @item[:user_id]) || User.lookup(id: 1)
end
def signal_notifications_enabled?
Setting.get('signal_notification_enabled') == true
end
def signal_channel
@signal_channel ||= begin
channel_id = Setting.get('signal_notification_channel_id')
return unless channel_id
Channel.find_by(id: channel_id, area: 'Signal::Number', active: true)
end
end
def collect_signal_recipients
recipients = []
possible_recipients = possible_recipients_of_group(ticket.group_id)
mention_users = Mention.where(mentionable_type: @item[:object], mentionable_id: @item[:object_id]).map(&:user)
mention_users.each do |user|
next if !user.group_access?(ticket.group_id, 'read')
possible_recipients.push(user)
end
if ticket.owner_id != 1
possible_recipients.push(ticket.owner)
end
possible_recipients_with_ooo = Set.new(possible_recipients)
possible_recipients.each do |user|
add_out_of_office_replacement(user, possible_recipients_with_ooo)
end
possible_recipients_with_ooo.each do |user|
next if recipient_is_current_user?(user)
next if !user.active?
next if user_signal_uid(user).blank?
next if !user_wants_signal_for_event?(user)
recipients.push(user)
end
recipients.uniq(&:id)
end
def possible_recipients_of_group(group_id)
Rails.cache.fetch("User/signal_notification/possible_recipients_of_group/#{group_id}/#{User.latest_change}", expires_in: 20.seconds) do
User.group_access(group_id, 'full').sort_by(&:login)
end
end
def add_out_of_office_replacement(user, recipients)
replacement = user.out_of_office_agent
return unless replacement
return unless TicketPolicy.new(replacement, ticket).agent_read_access?
recipients.add(replacement)
end
def recipient_is_current_user?(user)
return false if @params[:interface_handle] != 'application_server'
return true if article&.updated_by_id == user.id
return true if !article && @item[:user_id] == user.id
false
end
def user_signal_uid(user)
user.preferences.dig('notification_config', 'signal_uid').presence
end
def user_wants_signal_for_event?(user)
event_type = @item[:type]
return false if event_type.blank?
event_key = case event_type
when 'create' then 'create'
when 'update', 'update.merged_into', 'update.received_merge', 'update.reaction' then 'update'
when 'reminder_reached' then 'reminder_reached'
when 'escalation', 'escalation_warning' then 'escalation'
else return false
end
user.preferences.dig('notification_config', 'matrix', event_key, 'channel', 'signal') == true
end
end