Group refinements
This commit is contained in:
parent
c8ccee7ada
commit
f20cd5a53c
15 changed files with 287 additions and 23 deletions
62
apps/bridge-worker/scripts/test-zammad-group-webhook.ts
Executable file
62
apps/bridge-worker/scripts/test-zammad-group-webhook.ts
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/usr/bin/env tsx
|
||||||
|
/**
|
||||||
|
* Test script to verify Zammad group webhook integration
|
||||||
|
*
|
||||||
|
* This tests the group_created event webhook that is sent from bridge-worker
|
||||||
|
* to Zammad when a Signal group is created.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Using native fetch which is available in Node.js 18+
|
||||||
|
|
||||||
|
const ZAMMAD_URL = process.env.ZAMMAD_URL || 'http://localhost:8001';
|
||||||
|
const CHANNEL_TOKEN = process.env.CHANNEL_TOKEN || 'test-token';
|
||||||
|
|
||||||
|
async function testGroupWebhook() {
|
||||||
|
console.log('Testing Zammad Signal group webhook...\n');
|
||||||
|
|
||||||
|
const webhookUrl = `${ZAMMAD_URL}/api/v1/channels_cdr_signal_webhook/${CHANNEL_TOKEN}`;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
event: 'group_created',
|
||||||
|
conversation_id: '12345', // This would be a real ticket number
|
||||||
|
original_recipient: '+1234567890',
|
||||||
|
group_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Webhook URL:', webhookUrl);
|
||||||
|
console.log('Payload:', JSON.stringify(payload, null, 2));
|
||||||
|
console.log('\nSending request...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(webhookUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseText = await response.text();
|
||||||
|
console.log('Response Status:', response.status);
|
||||||
|
console.log('Response Headers:', [...response.headers.entries()]);
|
||||||
|
console.log('Response Body:', responseText);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log('\n✅ Webhook test successful!');
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(responseText);
|
||||||
|
console.log('Parsed response:', data);
|
||||||
|
} catch (e) {
|
||||||
|
// Response might not be JSON
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('\n❌ Webhook test failed!');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('\n❌ Error testing webhook:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run the test
|
||||||
|
testGroupWebhook();
|
||||||
67
docker-compose.bridge-dev.yml
Normal file
67
docker-compose.bridge-dev.yml
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
zammad-railsserver:
|
||||||
|
volumes:
|
||||||
|
# Controllers
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_signal_controller.rb:/opt/zammad/app/controllers/channels_cdr_signal_controller.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_voice_controller.rb:/opt/zammad/app/controllers/channels_cdr_voice_controller.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/controllers/channels_cdr_whatsapp_controller.rb:/opt/zammad/app/controllers/channels_cdr_whatsapp_controller.rb:ro
|
||||||
|
|
||||||
|
# Models
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/channel/driver/cdr_signal.rb:/opt/zammad/app/models/channel/driver/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/channel/driver/cdr_whatsapp.rb:/opt/zammad/app/models/channel/driver/cdr_whatsapp.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/ticket/article/enqueue_communicate_cdr_signal_job.rb:/opt/zammad/app/models/ticket/article/enqueue_communicate_cdr_signal_job.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/ticket/article/enqueue_communicate_cdr_whatsapp_job.rb:/opt/zammad/app/models/ticket/article/enqueue_communicate_cdr_whatsapp_job.rb:ro
|
||||||
|
|
||||||
|
# Jobs
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/jobs/communicate_cdr_signal_job.rb:/opt/zammad/app/jobs/communicate_cdr_signal_job.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/jobs/communicate_cdr_whatsapp_job.rb:/opt/zammad/app/jobs/communicate_cdr_whatsapp_job.rb:ro
|
||||||
|
|
||||||
|
# Policies
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/policies/controllers/channels_cdr_signal_controller_policy.rb:/opt/zammad/app/policies/controllers/channels_cdr_signal_controller_policy.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/policies/controllers/channels_cdr_voice_controller_policy.rb:/opt/zammad/app/policies/controllers/channels_cdr_voice_controller_policy.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/policies/controllers/channels_cdr_whatsapp_controller_policy.rb:/opt/zammad/app/policies/controllers/channels_cdr_whatsapp_controller_policy.rb:ro
|
||||||
|
|
||||||
|
# Config - initializers
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/initializers/cdr_signal.rb:/opt/zammad/config/initializers/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/initializers/cdr_whatsapp.rb:/opt/zammad/config/initializers/cdr_whatsapp.rb:ro
|
||||||
|
|
||||||
|
# Config - routes
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/routes/channel_cdr_signal.rb:/opt/zammad/config/routes/channel_cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/routes/channel_cdr_voice.rb:/opt/zammad/config/routes/channel_cdr_voice.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/routes/channel_cdr_whatsapp.rb:/opt/zammad/config/routes/channel_cdr_whatsapp.rb:ro
|
||||||
|
|
||||||
|
# Database migrations
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/db/addon/bridge/20210525091356_cdr_signal_channel.rb:/opt/zammad/db/addon/bridge/20210525091356_cdr_signal_channel.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/db/addon/bridge/20210525091357_cdr_voice_channel.rb:/opt/zammad/db/addon/bridge/20210525091357_cdr_voice_channel.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/db/addon/bridge/20210525091358_cdr_whatsapp_channel.rb:/opt/zammad/db/addon/bridge/20210525091358_cdr_whatsapp_channel.rb:ro
|
||||||
|
|
||||||
|
# Lib files
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_signal.rb:/opt/zammad/lib/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_signal_api.rb:/opt/zammad/lib/cdr_signal_api.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_whatsapp.rb:/opt/zammad/lib/cdr_whatsapp.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_whatsapp_api.rb:/opt/zammad/lib/cdr_whatsapp_api.rb:ro
|
||||||
|
|
||||||
|
# Also map to scheduler for background jobs
|
||||||
|
zammad-scheduler:
|
||||||
|
volumes:
|
||||||
|
# Models
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/channel/driver/cdr_signal.rb:/opt/zammad/app/models/channel/driver/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/channel/driver/cdr_whatsapp.rb:/opt/zammad/app/models/channel/driver/cdr_whatsapp.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/ticket/article/enqueue_communicate_cdr_signal_job.rb:/opt/zammad/app/models/ticket/article/enqueue_communicate_cdr_signal_job.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/models/ticket/article/enqueue_communicate_cdr_whatsapp_job.rb:/opt/zammad/app/models/ticket/article/enqueue_communicate_cdr_whatsapp_job.rb:ro
|
||||||
|
|
||||||
|
# Jobs
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/jobs/communicate_cdr_signal_job.rb:/opt/zammad/app/jobs/communicate_cdr_signal_job.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/app/jobs/communicate_cdr_whatsapp_job.rb:/opt/zammad/app/jobs/communicate_cdr_whatsapp_job.rb:ro
|
||||||
|
|
||||||
|
# Config - initializers
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/initializers/cdr_signal.rb:/opt/zammad/config/initializers/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/config/initializers/cdr_whatsapp.rb:/opt/zammad/config/initializers/cdr_whatsapp.rb:ro
|
||||||
|
|
||||||
|
# Lib files
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_signal.rb:/opt/zammad/lib/cdr_signal.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_signal_api.rb:/opt/zammad/lib/cdr_signal_api.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_whatsapp.rb:/opt/zammad/lib/cdr_whatsapp.rb:ro
|
||||||
|
- ${PWD}/packages/zammad-addon-bridge/src/lib/cdr_whatsapp_api.rb:/opt/zammad/lib/cdr_whatsapp_api.rb:ro
|
||||||
|
|
@ -20,6 +20,7 @@ x-bridge-vars: &common-bridge-variables
|
||||||
NEXTAUTH_URL: ${BRIDGE_DOMAIN}
|
NEXTAUTH_URL: ${BRIDGE_DOMAIN}
|
||||||
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
|
||||||
BRIDGE_SIGNAL_URL: ${BRIDGE_SIGNAL_URL}
|
BRIDGE_SIGNAL_URL: ${BRIDGE_SIGNAL_URL}
|
||||||
|
BRIDGE_SIGNAL_AUTO_GROUPS: ${BRIDGE_SIGNAL_AUTO_GROUPS}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
bridge-frontend:
|
bridge-frontend:
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
const { spawn } = require("child_process");
|
const { spawn } = require("child_process");
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
const app = process.argv[2];
|
const app = process.argv[2];
|
||||||
const command = process.argv[3];
|
const command = process.argv[3];
|
||||||
|
|
@ -16,10 +17,19 @@ const files = {
|
||||||
zammad: ["zammad", "postgresql", "opensearch"],
|
zammad: ["zammad", "postgresql", "opensearch"],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const envFile = path.resolve(process.cwd(), '.env');
|
||||||
const finalFiles = files[app]
|
const finalFiles = files[app]
|
||||||
.map((file) => ['-f', `docker/compose/${file}.yml`]).flat();
|
.map((file) => ['-f', `docker/compose/${file}.yml`]).flat();
|
||||||
|
|
||||||
|
// Add bridge-dev.yml for dev commands that include zammad
|
||||||
|
const devAppsWithZammad = ['linkDev', 'bridgeDev', 'all'];
|
||||||
|
if (devAppsWithZammad.includes(app) && files[app].includes('zammad')) {
|
||||||
|
finalFiles.push('-f', 'docker-compose.bridge-dev.yml');
|
||||||
|
}
|
||||||
|
|
||||||
const finalCommand = command === "up" ? ["up", "-d", "--remove-orphans"] : [command];
|
const finalCommand = command === "up" ? ["up", "-d", "--remove-orphans"] : [command];
|
||||||
const dockerCompose = spawn('docker', ['compose', '--env-file', '.env', ...finalFiles, ...finalCommand]);
|
const dockerCompose = spawn('docker', ['compose', '--env-file', envFile, ...finalFiles, ...finalCommand]);
|
||||||
|
|
||||||
dockerCompose.stdout.on('data', (data) => {
|
dockerCompose.stdout.on('data', (data) => {
|
||||||
console.info(`${data}`);
|
console.info(`${data}`);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'json'
|
||||||
|
require 'base64'
|
||||||
|
|
||||||
ROOT = '/opt/zammad'
|
ROOT = '/opt/zammad'
|
||||||
|
|
||||||
def _read_file(file, fullpath: false)
|
def _read_file(file, fullpath: false)
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,8 @@
|
||||||
"docker:bridge:dev:down": "node docker/scripts/docker.js bridgeDev down",
|
"docker:bridge:dev:down": "node docker/scripts/docker.js bridgeDev down",
|
||||||
"docker:bridge:up": "node docker/scripts/docker.js bridge up",
|
"docker:bridge:up": "node docker/scripts/docker.js bridge up",
|
||||||
"docker:bridge:down": "node docker/scripts/docker.js bridge down",
|
"docker:bridge:down": "node docker/scripts/docker.js bridge down",
|
||||||
"docker:bridge:build": "node docker/scripts/docker.js bridge build"
|
"docker:bridge:build": "node docker/scripts/docker.js bridge build",
|
||||||
|
"docker:zammad:restart": "docker restart zammad-railsserver zammad-scheduler"
|
||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"apps/*",
|
"apps/*",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class Index extends App.ControllerSubContent
|
class ChannelCdrSignal extends App.ControllerSubContent
|
||||||
requiredPermission: 'admin.channel_cdr_signal'
|
requiredPermission: 'admin.channel_cdr_signal'
|
||||||
events:
|
events:
|
||||||
'click .js-new': 'new'
|
'click .js-new': 'new'
|
||||||
|
|
@ -246,4 +246,4 @@ class FormEdit extends App.ControllerModal
|
||||||
@el.find('.alert').removeClass('hidden').text(error_message)
|
@el.find('.alert').removeClass('hidden').text(error_message)
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('cdr_signal', { prio: 5100, name: 'Signal', parent: '#channels', target: '#channels/cdr_signal', controller: Index, permission: ['admin.channel_cdr_signal'] }, 'NavBarAdmin')
|
App.Config.set('cdr_signal', { prio: 5100, name: 'Signal', parent: '#channels', target: '#channels/cdr_signal', controller: ChannelCdrSignal, permission: ['admin.channel_cdr_signal'] }, 'NavBarAdmin')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class Index extends App.ControllerSubContent
|
class ChannelCdrVoice extends App.ControllerSubContent
|
||||||
requiredPermission: 'admin.channel_cdr_voice'
|
requiredPermission: 'admin.channel_cdr_voice'
|
||||||
events:
|
events:
|
||||||
'click .js-new': 'new'
|
'click .js-new': 'new'
|
||||||
|
|
@ -246,4 +246,4 @@ class FormEdit extends App.ControllerModal
|
||||||
@el.find('.alert').removeClass('hidden').text(error_message)
|
@el.find('.alert').removeClass('hidden').text(error_message)
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('cdr_voice', { prio: 5100, name: 'Voice', parent: '#channels', target: '#channels/cdr_voice', controller: Index, permission: ['admin.channel_cdr_voice'] }, 'NavBarAdmin')
|
App.Config.set('cdr_voice', { prio: 5100, name: 'Voice', parent: '#channels', target: '#channels/cdr_voice', controller: ChannelCdrVoice, permission: ['admin.channel_cdr_voice'] }, 'NavBarAdmin')
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class Index extends App.ControllerSubContent
|
class ChannelCdrWhatsapp extends App.ControllerSubContent
|
||||||
requiredPermission: 'admin.channel_cdr_whatsapp'
|
requiredPermission: 'admin.channel_cdr_whatsapp'
|
||||||
events:
|
events:
|
||||||
'click .js-new': 'new'
|
'click .js-new': 'new'
|
||||||
|
|
@ -246,4 +246,4 @@ class FormEdit extends App.ControllerModal
|
||||||
@el.find('.alert').removeClass('hidden').text(error_message)
|
@el.find('.alert').removeClass('hidden').text(error_message)
|
||||||
)
|
)
|
||||||
|
|
||||||
App.Config.set('cdr_whatsapp', { prio: 5100, name: 'Whatsapp', parent: '#channels', target: '#channels/cdr_whatsapp', controller: Index, permission: ['admin.channel_cdr_whatsapp'] }, 'NavBarAdmin')
|
App.Config.set('cdr_whatsapp', { prio: 5100, name: 'Whatsapp', parent: '#channels', target: '#channels/cdr_whatsapp', controller: ChannelCdrWhatsapp, permission: ['admin.channel_cdr_whatsapp'] }, 'NavBarAdmin')
|
||||||
|
|
|
||||||
|
|
@ -117,6 +117,11 @@ class ChannelsCdrSignalController < ApplicationController
|
||||||
return render json: {}, status: 401 if !channel || !channel.active
|
return render json: {}, status: 401 if !channel || !channel.active
|
||||||
return render json: {}, status: 401 if channel.options[:token] != token
|
return render json: {}, status: 401 if channel.options[:token] != token
|
||||||
|
|
||||||
|
# Handle group creation events
|
||||||
|
if params[:event] == 'group_created'
|
||||||
|
return update_group
|
||||||
|
end
|
||||||
|
|
||||||
channel_id = channel.id
|
channel_id = channel.id
|
||||||
|
|
||||||
# validate input
|
# validate input
|
||||||
|
|
@ -141,6 +146,17 @@ class ChannelsCdrSignalController < ApplicationController
|
||||||
|
|
||||||
receiver_phone_number = params[:to].strip
|
receiver_phone_number = params[:to].strip
|
||||||
sender_phone_number = params[:from].strip
|
sender_phone_number = params[:from].strip
|
||||||
|
|
||||||
|
# Check if this is a group message using the isGroup 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
|
||||||
|
# Note: We can't rely on UUID format alone because users with privacy settings
|
||||||
|
# may have UUID identifiers instead of phone numbers
|
||||||
|
is_group_message = params[:isGroup] == true || params[:is_group] == true
|
||||||
|
# If it's a group message, the 'to' field contains the group ID
|
||||||
|
group_id = is_group_message ? params[:to] : nil
|
||||||
|
|
||||||
customer = User.find_by(phone: sender_phone_number)
|
customer = User.find_by(phone: sender_phone_number)
|
||||||
customer ||= User.find_by(mobile: sender_phone_number)
|
customer ||= User.find_by(mobile: sender_phone_number)
|
||||||
unless customer
|
unless customer
|
||||||
|
|
@ -192,13 +208,28 @@ class ChannelsCdrSignalController < ApplicationController
|
||||||
|
|
||||||
# find ticket or create one
|
# find ticket or create one
|
||||||
state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
|
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
|
|
||||||
|
# For group messages, find ticket by group_id in preferences
|
||||||
|
if is_group_message
|
||||||
|
ticket = Ticket.joins("LEFT JOIN tickets AS t2 ON t2.preferences::jsonb -> 'cdr_signal' ->> 'group_id' = '#{group_id}'")
|
||||||
|
.where(customer_id: customer.id)
|
||||||
|
.where.not(state_id: state_ids)
|
||||||
|
.where("tickets.preferences::jsonb -> 'cdr_signal' ->> 'group_id' = ?", group_id)
|
||||||
|
.order(:updated_at)
|
||||||
|
.first
|
||||||
|
else
|
||||||
|
ticket = Ticket.where(customer_id: customer.id).where.not(state_id: state_ids).order(:updated_at).first
|
||||||
|
end
|
||||||
|
|
||||||
if ticket
|
if ticket
|
||||||
# check if title need to be updated
|
# check if title need to be updated
|
||||||
ticket.title = title if ticket.title == '-'
|
ticket.title = title if ticket.title == '-'
|
||||||
new_state = Ticket::State.find_by(default_create: true)
|
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
|
ticket.state = Ticket::State.find_by(default_follow_up: true) if ticket.state_id != new_state.id
|
||||||
else
|
else
|
||||||
|
# Set up chat_id based on whether this is a group message
|
||||||
|
chat_id = is_group_message ? group_id : sender_phone_number
|
||||||
|
|
||||||
ticket = Ticket.new(
|
ticket = Ticket.new(
|
||||||
group_id: channel.group_id,
|
group_id: channel.group_id,
|
||||||
title: title,
|
title: title,
|
||||||
|
|
@ -207,10 +238,15 @@ class ChannelsCdrSignalController < ApplicationController
|
||||||
channel_id: channel.id,
|
channel_id: channel.id,
|
||||||
cdr_signal: {
|
cdr_signal: {
|
||||||
bot_token: channel.options[:bot_token], # change to bot id
|
bot_token: channel.options[:bot_token], # change to bot id
|
||||||
chat_id: sender_phone_number
|
chat_id: chat_id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Store group_id if this is a group message
|
||||||
|
if is_group_message
|
||||||
|
ticket.preferences[:cdr_signal][:group_id] = group_id
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
ticket.save!
|
ticket.save!
|
||||||
|
|
@ -265,4 +301,66 @@ class ChannelsCdrSignalController < ApplicationController
|
||||||
|
|
||||||
render json: result, status: :ok
|
render json: result, status: :ok
|
||||||
end
|
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
|
||||||
|
ticket = if params[:conversation_id].to_s.match?(/^\d+$/)
|
||||||
|
Ticket.find_by(id: params[:conversation_id])
|
||||||
|
else
|
||||||
|
Ticket.find_by(number: params[:conversation_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
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][:group_id] = params[:group_id]
|
||||||
|
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?
|
||||||
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,7 @@ class CommunicateCdrSignalJob < ApplicationJob
|
||||||
has_error = false
|
has_error = false
|
||||||
|
|
||||||
begin
|
begin
|
||||||
result = channel.deliver(
|
result = channel.deliver(article)
|
||||||
to: ticket.preferences[:cdr_signal][:chat_id],
|
|
||||||
body: article.body
|
|
||||||
)
|
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
log_error(article, e.message)
|
log_error(article, e.message)
|
||||||
has_error = true
|
has_error = true
|
||||||
|
|
|
||||||
|
|
@ -43,10 +43,7 @@ class CommunicateCdrWhatsappJob < ApplicationJob
|
||||||
has_error = false
|
has_error = false
|
||||||
|
|
||||||
begin
|
begin
|
||||||
result = channel.deliver(
|
result = channel.deliver(article)
|
||||||
to: ticket.preferences[:cdr_whatsapp][:chat_id],
|
|
||||||
body: article.body
|
|
||||||
)
|
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
log_error(article, e.message)
|
log_error(article, e.message)
|
||||||
has_error = true
|
has_error = true
|
||||||
|
|
|
||||||
|
|
@ -312,8 +312,21 @@ class CdrSignal
|
||||||
def from_article(article)
|
def from_article(article)
|
||||||
# sends a message from a zammad article
|
# sends a message from a zammad article
|
||||||
|
|
||||||
Rails.logger.debug { "Create signal message from article to '#{article[:to]}'..." }
|
Rails.logger.debug { "Create signal message from article..." }
|
||||||
|
|
||||||
@api.send_message(article[:to], article[:body])
|
# Get the recipient from ticket preferences
|
||||||
|
ticket = Ticket.find_by(id: article.ticket_id)
|
||||||
|
raise "No ticket found for article #{article.id}" unless ticket
|
||||||
|
|
||||||
|
recipient = ticket.preferences.dig('cdr_signal', 'chat_id')
|
||||||
|
raise "No Signal chat_id found in ticket preferences" unless recipient
|
||||||
|
|
||||||
|
Rails.logger.debug { "Sending to recipient: '#{recipient}'" }
|
||||||
|
|
||||||
|
# Include ticket ID as conversationId for group creation callback
|
||||||
|
options = {}
|
||||||
|
options[:conversationId] = ticket.number if ticket
|
||||||
|
|
||||||
|
@api.send_message(recipient, article[:body], options)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,12 @@ class CdrSignalApi
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_message(recipient, text, options = {})
|
def send_message(recipient, text, options = {})
|
||||||
post('send', { to: recipient.to_s, message: text }.merge(parse_hash(options)))
|
# Don't encode conversationId with CGI
|
||||||
|
params = { to: recipient.to_s, message: text }
|
||||||
|
if options[:conversationId]
|
||||||
|
params[:conversationId] = options[:conversationId]
|
||||||
|
options.delete(:conversationId)
|
||||||
|
end
|
||||||
|
post('send', params.merge(parse_hash(options)))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -312,8 +312,17 @@ class CdrWhatsapp
|
||||||
def from_article(article)
|
def from_article(article)
|
||||||
# sends a message from a zammad article
|
# sends a message from a zammad article
|
||||||
|
|
||||||
Rails.logger.debug { "Create whatsapp message from article to '#{article[:to]}'..." }
|
Rails.logger.debug { "Create whatsapp message from article..." }
|
||||||
|
|
||||||
@api.send_message(article[:to], article[:body])
|
# Get the recipient from ticket preferences
|
||||||
|
ticket = Ticket.find_by(id: article.ticket_id)
|
||||||
|
raise "No ticket found for article #{article.id}" unless ticket
|
||||||
|
|
||||||
|
recipient = ticket.preferences.dig('cdr_whatsapp', 'chat_id')
|
||||||
|
raise "No WhatsApp chat_id found in ticket preferences" unless recipient
|
||||||
|
|
||||||
|
Rails.logger.debug { "Sending to recipient: '#{recipient}'" }
|
||||||
|
|
||||||
|
@api.send_message(recipient, article[:body])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue