mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-08 06:53:26 +00:00
Merge branch 'develop' into feat/voice-channel
This commit is contained in:
2
Gemfile
2
Gemfile
@@ -212,6 +212,8 @@ group :development do
|
|||||||
gem 'stackprof'
|
gem 'stackprof'
|
||||||
# Should install the associated chrome extension to view query logs
|
# Should install the associated chrome extension to view query logs
|
||||||
gem 'meta_request', '>= 0.8.3'
|
gem 'meta_request', '>= 0.8.3'
|
||||||
|
|
||||||
|
gem 'tidewave'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
|||||||
59
Gemfile.lock
59
Gemfile.lock
@@ -230,6 +230,35 @@ GEM
|
|||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
dry-cli (1.1.0)
|
dry-cli (1.1.0)
|
||||||
|
dry-configurable (1.3.0)
|
||||||
|
dry-core (~> 1.1)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-core (1.1.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
logger
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-inflector (1.2.0)
|
||||||
|
dry-initializer (3.2.0)
|
||||||
|
dry-logic (1.6.0)
|
||||||
|
bigdecimal
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
dry-core (~> 1.1)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-schema (1.14.1)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
dry-configurable (~> 1.0, >= 1.0.1)
|
||||||
|
dry-core (~> 1.1)
|
||||||
|
dry-initializer (~> 3.2)
|
||||||
|
dry-logic (~> 1.5)
|
||||||
|
dry-types (~> 1.8)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
|
dry-types (1.8.3)
|
||||||
|
bigdecimal (~> 3.0)
|
||||||
|
concurrent-ruby (~> 1.0)
|
||||||
|
dry-core (~> 1.0)
|
||||||
|
dry-inflector (~> 1.0)
|
||||||
|
dry-logic (~> 1.4)
|
||||||
|
zeitwerk (~> 2.6)
|
||||||
ecma-re-validator (0.4.0)
|
ecma-re-validator (0.4.0)
|
||||||
regexp_parser (~> 2.2)
|
regexp_parser (~> 2.2)
|
||||||
elastic-apm (4.6.2)
|
elastic-apm (4.6.2)
|
||||||
@@ -270,6 +299,13 @@ GEM
|
|||||||
net-http-persistent (~> 4.0)
|
net-http-persistent (~> 4.0)
|
||||||
faraday-retry (2.2.1)
|
faraday-retry (2.2.1)
|
||||||
faraday (~> 2.0)
|
faraday (~> 2.0)
|
||||||
|
fast-mcp (1.5.0)
|
||||||
|
addressable (~> 2.8)
|
||||||
|
base64
|
||||||
|
dry-schema (~> 1.14)
|
||||||
|
json (~> 2.0)
|
||||||
|
mime-types (~> 3.4)
|
||||||
|
rack (~> 3.1)
|
||||||
fcm (1.0.8)
|
fcm (1.0.8)
|
||||||
faraday (>= 1.0.0, < 3.0)
|
faraday (>= 1.0.0, < 3.0)
|
||||||
googleauth (~> 1)
|
googleauth (~> 1)
|
||||||
@@ -593,7 +629,7 @@ GEM
|
|||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.8.1)
|
racc (1.8.1)
|
||||||
rack (2.2.15)
|
rack (3.2.0)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-contrib (2.5.0)
|
rack-contrib (2.5.0)
|
||||||
@@ -602,19 +638,20 @@ GEM
|
|||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
rack-mini-profiler (3.2.0)
|
rack-mini-profiler (3.2.0)
|
||||||
rack (>= 1.2.0)
|
rack (>= 1.2.0)
|
||||||
rack-protection (3.2.0)
|
rack-protection (4.1.1)
|
||||||
base64 (>= 0.1.0)
|
base64 (>= 0.1.0)
|
||||||
rack (~> 2.2, >= 2.2.4)
|
logger (>= 1.6.0)
|
||||||
|
rack (>= 3.0.0, < 4)
|
||||||
rack-proxy (0.7.7)
|
rack-proxy (0.7.7)
|
||||||
rack
|
rack
|
||||||
rack-session (1.0.2)
|
rack-session (2.1.1)
|
||||||
rack (< 3)
|
base64 (>= 0.1.0)
|
||||||
|
rack (>= 3.0.0)
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rack-timeout (0.6.3)
|
rack-timeout (0.6.3)
|
||||||
rackup (1.0.1)
|
rackup (2.2.1)
|
||||||
rack (< 3)
|
rack (>= 3)
|
||||||
webrick
|
|
||||||
rails (7.1.5.2)
|
rails (7.1.5.2)
|
||||||
actioncable (= 7.1.5.2)
|
actioncable (= 7.1.5.2)
|
||||||
actionmailbox (= 7.1.5.2)
|
actionmailbox (= 7.1.5.2)
|
||||||
@@ -845,6 +882,10 @@ GEM
|
|||||||
telephone_number (1.4.20)
|
telephone_number (1.4.20)
|
||||||
test-prof (1.2.1)
|
test-prof (1.2.1)
|
||||||
thor (1.4.0)
|
thor (1.4.0)
|
||||||
|
tidewave (0.2.0)
|
||||||
|
fast-mcp (~> 1.5.0)
|
||||||
|
rack (>= 2.0)
|
||||||
|
rails (>= 7.1.0)
|
||||||
tilt (2.3.0)
|
tilt (2.3.0)
|
||||||
time_diff (0.3.0)
|
time_diff (0.3.0)
|
||||||
activesupport
|
activesupport
|
||||||
@@ -898,7 +939,6 @@ GEM
|
|||||||
addressable (>= 2.8.0)
|
addressable (>= 2.8.0)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff (>= 0.4.0, < 2.0.0)
|
hashdiff (>= 0.4.0, < 2.0.0)
|
||||||
webrick (1.9.1)
|
|
||||||
websocket-driver (0.7.7)
|
websocket-driver (0.7.7)
|
||||||
base64
|
base64
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
@@ -1042,6 +1082,7 @@ DEPENDENCIES
|
|||||||
stripe
|
stripe
|
||||||
telephone_number
|
telephone_number
|
||||||
test-prof
|
test-prof
|
||||||
|
tidewave
|
||||||
time_diff
|
time_diff
|
||||||
twilio-ruby
|
twilio-ruby
|
||||||
twitty (~> 0.1.5)
|
twitty (~> 0.1.5)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
3.4.2
|
3.4.3
|
||||||
|
|||||||
@@ -50,35 +50,11 @@ const updateCampaignReadStatus = baseDomain => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const sanitizeURL = url => {
|
|
||||||
if (url === '') return '';
|
|
||||||
|
|
||||||
try {
|
|
||||||
// any invalid url will not be accepted
|
|
||||||
// example - JaVaScRiP%0at:alert(document.domain)"
|
|
||||||
// this has an obfuscated javascript protocol
|
|
||||||
const parsedURL = new URL(url);
|
|
||||||
|
|
||||||
// filter out dangerous protocols like `javascript`, `data`, `vbscript`
|
|
||||||
if (!['https', 'http'].includes(parsedURL.protocol)) {
|
|
||||||
throw new Error('Invalid Protocol');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.error('Invalid URL', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 'about:blank'; // blank page URL
|
|
||||||
};
|
|
||||||
|
|
||||||
export const IFrameHelper = {
|
export const IFrameHelper = {
|
||||||
getUrl({ baseUrl, websiteToken }) {
|
getUrl({ baseUrl, websiteToken }) {
|
||||||
baseUrl = sanitizeURL(baseUrl);
|
|
||||||
return `${baseUrl}/widget?website_token=${websiteToken}`;
|
return `${baseUrl}/widget?website_token=${websiteToken}`;
|
||||||
},
|
},
|
||||||
createFrame: ({ baseUrl, websiteToken }) => {
|
createFrame: ({ baseUrl, websiteToken }) => {
|
||||||
baseUrl = sanitizeURL(baseUrl);
|
|
||||||
|
|
||||||
if (IFrameHelper.getAppFrame()) {
|
if (IFrameHelper.getAppFrame()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -126,12 +102,10 @@ export const IFrameHelper = {
|
|||||||
window.onmessage = e => {
|
window.onmessage = e => {
|
||||||
if (
|
if (
|
||||||
typeof e.data !== 'string' ||
|
typeof e.data !== 'string' ||
|
||||||
e.data.indexOf('chatwoot-widget:') !== 0 ||
|
e.data.indexOf('chatwoot-widget:') !== 0
|
||||||
e.origin !== window.location.origin
|
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const message = JSON.parse(e.data.replace('chatwoot-widget:', ''));
|
const message = JSON.parse(e.data.replace('chatwoot-widget:', ''));
|
||||||
if (typeof IFrameHelper.events[message.event] === 'function') {
|
if (typeof IFrameHelper.events[message.event] === 'function') {
|
||||||
IFrameHelper.events[message.event](message);
|
IFrameHelper.events[message.event](message);
|
||||||
@@ -166,9 +140,7 @@ export const IFrameHelper = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setupAudioListeners: () => {
|
setupAudioListeners: () => {
|
||||||
let { baseUrl = '' } = window.$chatwoot;
|
const { baseUrl = '' } = window.$chatwoot;
|
||||||
baseUrl = sanitizeURL(baseUrl);
|
|
||||||
|
|
||||||
getAlertAudio(baseUrl, { type: 'widget', alertTone: 'ding' }).then(() =>
|
getAlertAudio(baseUrl, { type: 'widget', alertTone: 'ding' }).then(() =>
|
||||||
initOnEvents.forEach(event => {
|
initOnEvents.forEach(event => {
|
||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
@@ -262,7 +234,6 @@ export const IFrameHelper = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
popoutChatWindow: ({ baseUrl, websiteToken, locale }) => {
|
popoutChatWindow: ({ baseUrl, websiteToken, locale }) => {
|
||||||
baseUrl = sanitizeURL(baseUrl);
|
|
||||||
const cwCookie = Cookies.get('cw_conversation');
|
const cwCookie = Cookies.get('cw_conversation');
|
||||||
window.$chatwoot.toggle('close');
|
window.$chatwoot.toggle('close');
|
||||||
popoutChatWindow(baseUrl, websiteToken, locale, cwCookie);
|
popoutChatWindow(baseUrl, websiteToken, locale, cwCookie);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
# Table name: assignment_policies
|
# Table name: assignment_policies
|
||||||
#
|
#
|
||||||
# id :bigint not null, primary key
|
# id :bigint not null, primary key
|
||||||
# assignment_order :integer default(0), not null
|
# assignment_order :integer default("round_robin"), not null
|
||||||
# conversation_priority :integer default("earliest_created"), not null
|
# conversation_priority :integer default("earliest_created"), not null
|
||||||
# description :text
|
# description :text
|
||||||
# enabled :boolean default(TRUE), not null
|
# enabled :boolean default(TRUE), not null
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ module Liquidable
|
|||||||
|
|
||||||
included do
|
included do
|
||||||
before_create :process_liquid_in_content
|
before_create :process_liquid_in_content
|
||||||
|
before_create :process_liquid_in_template_params
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -35,4 +36,61 @@ module Liquidable
|
|||||||
# We don't want to process liquid in code blocks
|
# We don't want to process liquid in code blocks
|
||||||
content.gsub(/`(.*?)`/m, '{% raw %}`\\1`{% endraw %}')
|
content.gsub(/`(.*?)`/m, '{% raw %}`\\1`{% endraw %}')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_liquid_in_template_params
|
||||||
|
return unless template_params_present? && liquid_processable_template_params?
|
||||||
|
|
||||||
|
processed_params = process_liquid_in_hash(template_params_data['processed_params'])
|
||||||
|
|
||||||
|
# Update the additional_attributes with processed template_params
|
||||||
|
self.additional_attributes = additional_attributes.merge(
|
||||||
|
'template_params' => template_params_data.merge('processed_params' => processed_params)
|
||||||
|
)
|
||||||
|
rescue Liquid::Error
|
||||||
|
# If there is an error in the liquid syntax, we don't want to process it
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_params_present?
|
||||||
|
additional_attributes&.dig('template_params', 'processed_params').present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def liquid_processable_template_params?
|
||||||
|
message_type == 'outgoing' || message_type == 'template'
|
||||||
|
end
|
||||||
|
|
||||||
|
def template_params_data
|
||||||
|
additional_attributes['template_params']
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_liquid_in_hash(hash)
|
||||||
|
return hash unless hash.is_a?(Hash)
|
||||||
|
|
||||||
|
hash.transform_values { |value| process_liquid_value(value) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_liquid_value(value)
|
||||||
|
case value
|
||||||
|
when String
|
||||||
|
process_liquid_string(value)
|
||||||
|
when Hash
|
||||||
|
process_liquid_in_hash(value)
|
||||||
|
when Array
|
||||||
|
process_liquid_array(value)
|
||||||
|
else
|
||||||
|
value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_liquid_array(array)
|
||||||
|
array.map { |item| process_liquid_value(item) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_liquid_string(string)
|
||||||
|
return string if string.blank?
|
||||||
|
|
||||||
|
template = Liquid::Template.parse(string)
|
||||||
|
template.render(message_drops)
|
||||||
|
rescue Liquid::Error
|
||||||
|
string
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
# idx_notifications_performance (user_id,account_id,snoozed_until,read_at)
|
||||||
# index_notifications_on_account_id (account_id)
|
# index_notifications_on_account_id (account_id)
|
||||||
# index_notifications_on_last_activity_at (last_activity_at)
|
# index_notifications_on_last_activity_at (last_activity_at)
|
||||||
# index_notifications_on_user_id (user_id)
|
# index_notifications_on_user_id (user_id)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
shared: &shared
|
shared: &shared
|
||||||
version: '4.5.0'
|
version: '4.5.2'
|
||||||
|
|
||||||
development:
|
development:
|
||||||
<<: *shared
|
<<: *shared
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
class AddTemplateParamsToCampaigns < ActiveRecord::Migration[7.1]
|
class AddTemplateParamsToCampaigns < ActiveRecord::Migration[7.1]
|
||||||
def change
|
def change
|
||||||
add_column :campaigns, :template_params, :jsonb, default: {}, null: false
|
add_column :campaigns, :template_params, :jsonb
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
class AddFeatureCitationToAssistantConfig < ActiveRecord::Migration[7.1]
|
class AddFeatureCitationToAssistantConfig < ActiveRecord::Migration[7.1]
|
||||||
def up
|
def up
|
||||||
|
return unless ChatwootApp.enterprise?
|
||||||
|
|
||||||
Captain::Assistant.find_each do |assistant|
|
Captain::Assistant.find_each do |assistant|
|
||||||
assistant.update!(
|
assistant.update!(
|
||||||
config: assistant.config.merge('feature_citation' => true)
|
config: assistant.config.merge('feature_citation' => true)
|
||||||
@@ -8,6 +10,8 @@ class AddFeatureCitationToAssistantConfig < ActiveRecord::Migration[7.1]
|
|||||||
end
|
end
|
||||||
|
|
||||||
def down
|
def down
|
||||||
|
return unless ChatwootApp.enterprise?
|
||||||
|
|
||||||
Captain::Assistant.find_each do |assistant|
|
Captain::Assistant.find_each do |assistant|
|
||||||
config = assistant.config.dup
|
config = assistant.config.dup
|
||||||
config.delete('feature_citation')
|
config.delete('feature_citation')
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Description: Install and manage a Chatwoot installation.
|
# Description: Install and manage a Chatwoot installation.
|
||||||
# OS: Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS
|
# OS: Ubuntu 20.04 LTS, 22.04 LTS, 24.04 LTS
|
||||||
# Script Version: 3.4.2
|
# Script Version: 3.4.3
|
||||||
# Run this script as root
|
# Run this script as root
|
||||||
|
|
||||||
set -eu -o errexit -o pipefail -o noclobber -o nounset
|
set -eu -o errexit -o pipefail -o noclobber -o nounset
|
||||||
@@ -19,7 +19,7 @@ fi
|
|||||||
# option --output/-o requires 1 argument
|
# option --output/-o requires 1 argument
|
||||||
LONGOPTS=console,debug,help,install,Install:,logs:,restart,ssl,upgrade,Upgrade:,webserver,version,web-only,worker-only,convert:
|
LONGOPTS=console,debug,help,install,Install:,logs:,restart,ssl,upgrade,Upgrade:,webserver,version,web-only,worker-only,convert:
|
||||||
OPTIONS=cdhiI:l:rsuU:wvWK
|
OPTIONS=cdhiI:l:rsuU:wvWK
|
||||||
CWCTL_VERSION="3.3.0"
|
CWCTL_VERSION="3.4.3"
|
||||||
pg_pass=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15 ; echo '')
|
pg_pass=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 15 ; echo '')
|
||||||
CHATWOOT_HUB_URL="https://hub.2.chatwoot.com/events"
|
CHATWOOT_HUB_URL="https://hub.2.chatwoot.com/events"
|
||||||
|
|
||||||
@@ -430,7 +430,7 @@ function configure_systemd_services() {
|
|||||||
|
|
||||||
if [ "$DEPLOYMENT_TYPE" == "web" ]; then
|
if [ "$DEPLOYMENT_TYPE" == "web" ]; then
|
||||||
echo "Setting up web-only deployment"
|
echo "Setting up web-only deployment"
|
||||||
|
|
||||||
# Stop and disable existing services if converting
|
# Stop and disable existing services if converting
|
||||||
if [ "$existing_full_deployment" = true ]; then
|
if [ "$existing_full_deployment" = true ]; then
|
||||||
echo "Converting from full deployment to web-only"
|
echo "Converting from full deployment to web-only"
|
||||||
@@ -449,14 +449,14 @@ function configure_systemd_services() {
|
|||||||
|
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.1.service /etc/systemd/system/chatwoot-web.1.service
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.1.service /etc/systemd/system/chatwoot-web.1.service
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.target /etc/systemd/system/chatwoot-web.target
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.target /etc/systemd/system/chatwoot-web.target
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable chatwoot-web.target
|
systemctl enable chatwoot-web.target
|
||||||
systemctl start chatwoot-web.target
|
systemctl start chatwoot-web.target
|
||||||
|
|
||||||
elif [ "$DEPLOYMENT_TYPE" == "worker" ]; then
|
elif [ "$DEPLOYMENT_TYPE" == "worker" ]; then
|
||||||
echo "Setting up worker-only deployment"
|
echo "Setting up worker-only deployment"
|
||||||
|
|
||||||
# Stop and disable existing services if converting
|
# Stop and disable existing services if converting
|
||||||
if [ "$existing_full_deployment" = true ]; then
|
if [ "$existing_full_deployment" = true ]; then
|
||||||
echo "Converting from full deployment to worker-only"
|
echo "Converting from full deployment to worker-only"
|
||||||
@@ -475,14 +475,14 @@ function configure_systemd_services() {
|
|||||||
|
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.1.service /etc/systemd/system/chatwoot-worker.1.service
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.1.service /etc/systemd/system/chatwoot-worker.1.service
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.target /etc/systemd/system/chatwoot-worker.target
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.target /etc/systemd/system/chatwoot-worker.target
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
systemctl enable chatwoot-worker.target
|
systemctl enable chatwoot-worker.target
|
||||||
systemctl start chatwoot-worker.target
|
systemctl start chatwoot-worker.target
|
||||||
|
|
||||||
else
|
else
|
||||||
echo "Setting up full deployment (web + worker)"
|
echo "Setting up full deployment (web + worker)"
|
||||||
|
|
||||||
# Stop existing specialized deployments if converting back to full
|
# Stop existing specialized deployments if converting back to full
|
||||||
if [ -f "/etc/systemd/system/chatwoot-web.target" ]; then
|
if [ -f "/etc/systemd/system/chatwoot-web.target" ]; then
|
||||||
echo "Converting from web-only to full deployment"
|
echo "Converting from web-only to full deployment"
|
||||||
@@ -494,7 +494,7 @@ function configure_systemd_services() {
|
|||||||
systemctl stop chatwoot-worker.target || true
|
systemctl stop chatwoot-worker.target || true
|
||||||
systemctl disable chatwoot-worker.target || true
|
systemctl disable chatwoot-worker.target || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.1.service /etc/systemd/system/chatwoot-web.1.service
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-web.1.service /etc/systemd/system/chatwoot-web.1.service
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.1.service /etc/systemd/system/chatwoot-worker.1.service
|
cp /home/chatwoot/chatwoot/deployment/chatwoot-worker.1.service /etc/systemd/system/chatwoot-worker.1.service
|
||||||
cp /home/chatwoot/chatwoot/deployment/chatwoot.target /etc/systemd/system/chatwoot.target
|
cp /home/chatwoot/chatwoot/deployment/chatwoot.target /etc/systemd/system/chatwoot.target
|
||||||
@@ -538,7 +538,7 @@ function setup_ssl() {
|
|||||||
cd chatwoot
|
cd chatwoot
|
||||||
sed -i "s/http:\/\/0.0.0.0:3000/https:\/\/$domain_name/g" .env
|
sed -i "s/http:\/\/0.0.0.0:3000/https:\/\/$domain_name/g" .env
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Restart the appropriate chatwoot target
|
# Restart the appropriate chatwoot target
|
||||||
if [ -f "/etc/systemd/system/chatwoot-web.target" ]; then
|
if [ -f "/etc/systemd/system/chatwoot-web.target" ]; then
|
||||||
systemctl restart chatwoot-web.target
|
systemctl restart chatwoot-web.target
|
||||||
@@ -1005,7 +1005,7 @@ EOF
|
|||||||
upgrade_redis
|
upgrade_redis
|
||||||
upgrade_node
|
upgrade_node
|
||||||
get_pnpm
|
get_pnpm
|
||||||
|
|
||||||
sudo -i -u chatwoot << EOF
|
sudo -i -u chatwoot << EOF
|
||||||
|
|
||||||
# Navigate to the Chatwoot directory
|
# Navigate to the Chatwoot directory
|
||||||
@@ -1098,16 +1098,16 @@ function restart() {
|
|||||||
##############################################################################
|
##############################################################################
|
||||||
function convert_deployment() {
|
function convert_deployment() {
|
||||||
echo "Converting Chatwoot deployment to: $DEPLOYMENT_TYPE"
|
echo "Converting Chatwoot deployment to: $DEPLOYMENT_TYPE"
|
||||||
|
|
||||||
# Check if Chatwoot is installed
|
# Check if Chatwoot is installed
|
||||||
if [ ! -d "/home/chatwoot/chatwoot" ]; then
|
if [ ! -d "/home/chatwoot/chatwoot" ]; then
|
||||||
echo "Chatwoot installation not found. Use --install first."
|
echo "Chatwoot installation not found. Use --install first."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run the systemd service configuration which handles conversion logic
|
# Run the systemd service configuration which handles conversion logic
|
||||||
configure_systemd_services
|
configure_systemd_services
|
||||||
|
|
||||||
echo "Deployment converted successfully to: $DEPLOYMENT_TYPE"
|
echo "Deployment converted successfully to: $DEPLOYMENT_TYPE"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@chatwoot/chatwoot",
|
"name": "@chatwoot/chatwoot",
|
||||||
"version": "4.5.0",
|
"version": "4.5.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"eslint": "eslint app/**/*.{js,vue}",
|
"eslint": "eslint app/**/*.{js,vue}",
|
||||||
|
|||||||
@@ -69,4 +69,159 @@ shared_examples_for 'liqudable' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when liquid is present in template_params' do
|
||||||
|
let(:contact) do
|
||||||
|
create(:contact, name: 'john', email: 'john@example.com', phone_number: '+912883', custom_attributes: { customer_type: 'platinum' })
|
||||||
|
end
|
||||||
|
let(:conversation) { create(:conversation, id: 1, contact: contact, custom_attributes: { priority: 'high' }) }
|
||||||
|
|
||||||
|
context 'when message is outgoing with template_params' do
|
||||||
|
let(:message) { build(:message, conversation: conversation, message_type: 'outgoing') }
|
||||||
|
|
||||||
|
it 'replaces liquid variables in template_params body' do
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'greet',
|
||||||
|
'category' => 'MARKETING',
|
||||||
|
'language' => 'en',
|
||||||
|
'processed_params' => {
|
||||||
|
'body' => {
|
||||||
|
'customer_name' => '{{contact.name}}',
|
||||||
|
'customer_email' => '{{contact.email}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
body_params = message.additional_attributes['template_params']['processed_params']['body']
|
||||||
|
expect(body_params['customer_name']).to eq 'John'
|
||||||
|
expect(body_params['customer_email']).to eq 'john@example.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'replaces liquid variables in nested template_params' do
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'header' => {
|
||||||
|
'media_url' => 'https://example.com/{{contact.name}}.jpg'
|
||||||
|
},
|
||||||
|
'body' => {
|
||||||
|
'customer_name' => '{{contact.name}}',
|
||||||
|
'priority' => '{{conversation.custom_attribute.priority}}'
|
||||||
|
},
|
||||||
|
'footer' => {
|
||||||
|
'company' => '{{account.name}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
processed = message.additional_attributes['template_params']['processed_params']
|
||||||
|
expect(processed['header']['media_url']).to eq 'https://example.com/John.jpg'
|
||||||
|
expect(processed['body']['customer_name']).to eq 'John'
|
||||||
|
expect(processed['body']['priority']).to eq 'high'
|
||||||
|
expect(processed['footer']['company']).to eq conversation.account.name
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles arrays in template_params' do
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'buttons' => [
|
||||||
|
{ 'type' => 'url', 'parameter' => 'https://example.com/{{contact.name}}' },
|
||||||
|
{ 'type' => 'text', 'parameter' => 'Hello {{contact.name}}' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
buttons = message.additional_attributes['template_params']['processed_params']['buttons']
|
||||||
|
expect(buttons[0]['parameter']).to eq 'https://example.com/John'
|
||||||
|
expect(buttons[1]['parameter']).to eq 'Hello John'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles custom attributes in template_params' do
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'body' => {
|
||||||
|
'customer_type' => '{{contact.custom_attribute.customer_type}}',
|
||||||
|
'priority' => '{{conversation.custom_attribute.priority}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
body_params = message.additional_attributes['template_params']['processed_params']['body']
|
||||||
|
expect(body_params['customer_type']).to eq 'platinum'
|
||||||
|
expect(body_params['priority']).to eq 'high'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles missing email with default filter in template_params' do
|
||||||
|
contact.update!(email: nil)
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'body' => {
|
||||||
|
'customer_email' => '{{ contact.email | default: "no-email@example.com" }}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
body_params = message.additional_attributes['template_params']['processed_params']['body']
|
||||||
|
expect(body_params['customer_email']).to eq 'no-email@example.com'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'handles broken liquid syntax in template_params gracefully' do
|
||||||
|
message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'body' => {
|
||||||
|
'broken_liquid' => '{{contact.name} {{invalid}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message.save!
|
||||||
|
|
||||||
|
body_params = message.additional_attributes['template_params']['processed_params']['body']
|
||||||
|
expect(body_params['broken_liquid']).to eq '{{contact.name} {{invalid}}'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not process template_params when message is incoming' do
|
||||||
|
incoming_message = build(:message, conversation: conversation, message_type: 'incoming')
|
||||||
|
incoming_message.additional_attributes = {
|
||||||
|
'template_params' => {
|
||||||
|
'name' => 'test_template',
|
||||||
|
'processed_params' => {
|
||||||
|
'body' => {
|
||||||
|
'customer_name' => '{{contact.name}}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
incoming_message.save!
|
||||||
|
|
||||||
|
body_params = incoming_message.additional_attributes['template_params']['processed_params']['body']
|
||||||
|
expect(body_params['customer_name']).to eq '{{contact.name}}'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not process template_params when not present' do
|
||||||
|
message.additional_attributes = { 'other_data' => 'test' }
|
||||||
|
expect { message.save! }.not_to raise_error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user