From fb6fec167b7257f7b028dc662da03181d140c2b9 Mon Sep 17 00:00:00 2001 From: Gabriel Jablonski Date: Sun, 1 Feb 2026 14:25:06 -0300 Subject: [PATCH] chore: general improvements (#204) * chore: update scheduled messages author association to nullable and adjust related specs * chore: update sender handling for WhatsApp messages and add external sender name --- .../components-next/message/Message.vue | 10 +++++++++- .../dashboard/i18n/locale/en/conversation.json | 1 + .../i18n/locale/pt_BR/conversation.json | 1 + app/models/automation_rule.rb | 2 +- app/models/scheduled_message.rb | 6 +++--- app/models/user.rb | 2 +- .../baileys_handlers/messages_upsert.rb | 4 ++-- .../whatsapp/zapi_handlers/received_callback.rb | 4 ++-- ...nge_scheduled_messages_author_to_nullable.rb | 10 ++++++++++ db/schema.rb | 6 +++--- .../automation_rules_controller_spec.rb | 17 +++++++++++++++++ spec/models/user_spec.rb | 16 ++++++++++++++++ 12 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 db/migrate/20260201162122_change_scheduled_messages_author_to_nullable.rb diff --git a/app/javascript/dashboard/components-next/message/Message.vue b/app/javascript/dashboard/components-next/message/Message.vue index 6d0256beb..c2cea12de 100644 --- a/app/javascript/dashboard/components-next/message/Message.vue +++ b/app/javascript/dashboard/components-next/message/Message.vue @@ -433,8 +433,16 @@ function handleReplyTo() { } const avatarInfo = computed(() => { - // If no sender, return bot info + // If no sender, check for external sender name if (!props.sender) { + const externalSenderName = props.contentAttributes?.externalSenderName; + if (externalSenderName === 'WhatsApp') { + return { + name: t('CONVERSATION.WHATSAPP'), + src: '', + iconName: 'i-woot-whatsapp', + }; + } return { name: t('CONVERSATION.BOT'), src: '', diff --git a/app/javascript/dashboard/i18n/locale/en/conversation.json b/app/javascript/dashboard/i18n/locale/en/conversation.json index 6715f951d..1639b22f3 100644 --- a/app/javascript/dashboard/i18n/locale/en/conversation.json +++ b/app/javascript/dashboard/i18n/locale/en/conversation.json @@ -255,6 +255,7 @@ "MESSAGE_ERROR": "Unable to send this message, please try again later", "SENT_BY": "Sent by:", "BOT": "Bot", + "WHATSAPP": "WhatsApp", "SEND_FAILED": "Couldn't send message! Try again", "TRY_AGAIN": "retry", "ASSIGNMENT": { diff --git a/app/javascript/dashboard/i18n/locale/pt_BR/conversation.json b/app/javascript/dashboard/i18n/locale/pt_BR/conversation.json index aa65c89c7..13ebdd33a 100644 --- a/app/javascript/dashboard/i18n/locale/pt_BR/conversation.json +++ b/app/javascript/dashboard/i18n/locale/pt_BR/conversation.json @@ -254,6 +254,7 @@ "MESSAGE_ERROR": "Não foi possível enviar esta mensagem, por favor, tente novamente mais tarde", "SENT_BY": "Enviado por:", "BOT": "Robôs", + "WHATSAPP": "WhatsApp", "SEND_FAILED": "Não foi possível enviar a mensagem! Tente novamente", "TRY_AGAIN": "tentar novamente", "ASSIGNMENT": { diff --git a/app/models/automation_rule.rb b/app/models/automation_rule.rb index ba20bc0e5..46841c31b 100644 --- a/app/models/automation_rule.rb +++ b/app/models/automation_rule.rb @@ -22,7 +22,7 @@ class AutomationRule < ApplicationRecord include Reauthorizable belongs_to :account - has_many :scheduled_messages, as: :author, dependent: :destroy + has_many :scheduled_messages, as: :author, dependent: :nullify has_many_attached :files validate :json_conditions_format diff --git a/app/models/scheduled_message.rb b/app/models/scheduled_message.rb index c49205e7e..7b43fa20b 100644 --- a/app/models/scheduled_message.rb +++ b/app/models/scheduled_message.rb @@ -3,7 +3,7 @@ # Table name: scheduled_messages # # id :bigint not null, primary key -# author_type :string not null +# author_type :string # content :text # scheduled_at :datetime # status :integer default("draft"), not null @@ -11,7 +11,7 @@ # created_at :datetime not null # updated_at :datetime not null # account_id :bigint not null -# author_id :bigint not null +# author_id :bigint # conversation_id :bigint not null # inbox_id :bigint not null # message_id :bigint @@ -43,7 +43,7 @@ class ScheduledMessage < ApplicationRecord belongs_to :account belongs_to :inbox belongs_to :conversation - belongs_to :author, polymorphic: true + belongs_to :author, polymorphic: true, optional: true belongs_to :message, optional: true has_one_attached :attachment diff --git a/app/models/user.rb b/app/models/user.rb index 199f5f589..156452322 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -100,7 +100,7 @@ class User < ApplicationRecord has_many :messages, as: :sender, dependent: :nullify has_many :invitees, through: :account_users, class_name: 'User', foreign_key: 'inviter_id', source: :inviter, dependent: :nullify - has_many :scheduled_messages, as: :author, dependent: :destroy + has_many :scheduled_messages, as: :author, dependent: :nullify has_many :custom_filters, dependent: :destroy_async has_many :dashboard_apps, dependent: :nullify diff --git a/app/services/whatsapp/baileys_handlers/messages_upsert.rb b/app/services/whatsapp/baileys_handlers/messages_upsert.rb index 129cbb34d..96f5ecab9 100644 --- a/app/services/whatsapp/baileys_handlers/messages_upsert.rb +++ b/app/services/whatsapp/baileys_handlers/messages_upsert.rb @@ -93,8 +93,7 @@ module Whatsapp::BaileysHandlers::MessagesUpsert # rubocop:disable Metrics/Modul account_id: @inbox.account_id, inbox_id: @inbox.id, source_id: raw_message_id, - sender: incoming? ? @contact : @inbox.account.account_users.first.user, - sender_type: incoming? ? 'Contact' : 'User', + sender: incoming? ? @contact : nil, message_type: incoming? ? :incoming : :outgoing, content_attributes: message_content_attributes ) @@ -110,6 +109,7 @@ module Whatsapp::BaileysHandlers::MessagesUpsert # rubocop:disable Metrics/Modul type = message_type msg = unwrap_ephemeral_message(@raw_message[:message]) content_attributes = { external_created_at: baileys_extract_message_timestamp(@raw_message[:messageTimestamp]) } + content_attributes[:external_sender_name] = 'WhatsApp' unless incoming? if type == 'reaction' content_attributes[:in_reply_to_external_id] = msg.dig(:reactionMessage, :key, :id) content_attributes[:is_reaction] = true diff --git a/app/services/whatsapp/zapi_handlers/received_callback.rb b/app/services/whatsapp/zapi_handlers/received_callback.rb index 80e4a3b2c..540b6c64d 100644 --- a/app/services/whatsapp/zapi_handlers/received_callback.rb +++ b/app/services/whatsapp/zapi_handlers/received_callback.rb @@ -156,8 +156,7 @@ module Whatsapp::ZapiHandlers::ReceivedCallback # rubocop:disable Metrics/Module account_id: @inbox.account_id, inbox_id: @inbox.id, source_id: raw_message_id, - sender: incoming_message? ? @contact : @inbox.account.account_users.first.user, - sender_type: incoming_message? ? 'Contact' : 'User', + sender: incoming_message? ? @contact : nil, message_type: incoming_message? ? :incoming : :outgoing, content_attributes: message_content_attributes ) @@ -170,6 +169,7 @@ module Whatsapp::ZapiHandlers::ReceivedCallback # rubocop:disable Metrics/Module def message_content_attributes type = message_type content_attributes = { external_created_at: @raw_message[:momment] / 1000 } + content_attributes[:external_sender_name] = 'WhatsApp' unless incoming_message? if type == 'reaction' content_attributes[:in_reply_to_external_id] = @raw_message.dig(:reaction, :referencedMessage, :messageId) diff --git a/db/migrate/20260201162122_change_scheduled_messages_author_to_nullable.rb b/db/migrate/20260201162122_change_scheduled_messages_author_to_nullable.rb new file mode 100644 index 000000000..3b995eac1 --- /dev/null +++ b/db/migrate/20260201162122_change_scheduled_messages_author_to_nullable.rb @@ -0,0 +1,10 @@ +class ChangeScheduledMessagesAuthorToNullable < ActiveRecord::Migration[7.1] + def up + change_column_null :scheduled_messages, :author_id, true + change_column_null :scheduled_messages, :author_type, true + end + + def down + raise ActiveRecord::IrreversibleMigration, "Can't revert because there might be scheduled messages with null author_id/author_type" + end +end diff --git a/db/schema.rb b/db/schema.rb index e1c6ef1ed..57d3ae8b3 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2026_01_22_175206) do +ActiveRecord::Schema[7.1].define(version: 2026_02_01_162122) do # These extensions should be enabled to support this database enable_extension "pg_stat_statements" enable_extension "pg_trgm" @@ -1134,8 +1134,8 @@ ActiveRecord::Schema[7.1].define(version: 2026_01_22_175206) do t.bigint "account_id", null: false t.bigint "conversation_id", null: false t.bigint "inbox_id", null: false - t.string "author_type", null: false - t.bigint "author_id", null: false + t.string "author_type" + t.bigint "author_id" t.bigint "message_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false diff --git a/spec/controllers/api/v1/accounts/automation_rules_controller_spec.rb b/spec/controllers/api/v1/accounts/automation_rules_controller_spec.rb index ff882da4f..ac170d2c3 100644 --- a/spec/controllers/api/v1/accounts/automation_rules_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/automation_rules_controller_spec.rb @@ -443,6 +443,23 @@ RSpec.describe 'Api::V1::Accounts::AutomationRulesController', type: :request do expect(response).to have_http_status(:success) expect(account.automation_rules.count).to eq(0) end + + it 'deletes automation rule even when it has sent scheduled messages' do + conversation = create(:conversation, account: account, inbox: inbox, contact: contact) + scheduled_message = create(:scheduled_message, + account: account, + inbox: inbox, + conversation: conversation, + author: automation_rule) + scheduled_message.update_column(:status, ScheduledMessage.statuses[:sent]) # rubocop:disable Rails/SkipsModelValidations + + delete "/api/v1/accounts/#{account.id}/automation_rules/#{automation_rule.id}", + headers: administrator.create_new_auth_token + + expect(response).to have_http_status(:success) + expect(account.automation_rules.count).to eq(0) + expect(scheduled_message.reload.author_id).to be_nil + end end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 710402e36..f9eef4ef1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -282,4 +282,20 @@ RSpec.describe User do expect(user.signature_separator).to eq('--') end end + + describe 'destroy' do + it 'nullifies scheduled messages author when user has sent scheduled messages' do + account = create(:account) + create(:account_user, user: user, account: account) + inbox = create(:inbox, account: account) + contact = create(:contact, account: account) + conversation = create(:conversation, account: account, inbox: inbox, contact: contact) + scheduled_message = create(:scheduled_message, account: account, inbox: inbox, conversation: conversation, author: user) + scheduled_message.update_column(:status, ScheduledMessage.statuses[:sent]) # rubocop:disable Rails/SkipsModelValidations + + user.destroy! + + expect(scheduled_message.reload.author_id).to be_nil + end + end end