iachat/app/controllers/api/v1/accounts/conversations/messages_controller.rb
Gabriel Jablonski bce4e9b3a7
fix: clear source_id when retrying message to prevent skipping (#222)
* fix: clear source_id when retrying message to prevent skipping

* fix: validate message status and type before retrying to ensure proper handling
2026-02-24 14:45:07 -03:00

135 lines
4.6 KiB
Ruby

class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::Conversations::BaseController
include Events::Types
before_action :ensure_api_inbox, only: :update
def index
@messages = message_finder.perform
end
def create
user = Current.user || @resource
mb = Messages::MessageBuilder.new(user, @conversation, params)
@message = mb.perform
trigger_typing_event(CONVERSATION_TYPING_OFF)
rescue StandardError => e
render_could_not_create_error(e.message)
end
def update
Messages::StatusUpdateService.new(message, permitted_params[:status], permitted_params[:external_error]).perform
@message = message
end
def destroy
ActiveRecord::Base.transaction do
message.update!(content: I18n.t('conversations.messages.deleted'), content_type: :text, content_attributes: { deleted: true })
message.attachments.destroy_all
end
delete_message_on_channel
end
def retry
return if message.blank?
return head :unprocessable_entity unless message.failed? && (message.outgoing? || message.template?)
service = Messages::StatusUpdateService.new(message, 'sent')
service.perform
message.update!(content_attributes: {}, source_id: nil)
::SendReplyJob.perform_later(message.id)
rescue StandardError => e
render_could_not_create_error(e.message)
end
def translate
return head :ok if already_translated_content_available?
translated_content = Integrations::GoogleTranslate::ProcessorService.new(
message: message,
target_language: permitted_params[:target_language]
).perform
if translated_content.present?
translations = {}
translations[permitted_params[:target_language]] = translated_content
translations = message.translations.merge!(translations) if message.translations.present?
message.update!(translations: translations)
end
render json: { content: translated_content }
end
def edit_content
new_content = params[:content]
return render json: { error: 'Content is required' }, status: :unprocessable_entity if new_content.blank?
return render json: { error: 'Content exceeds maximum length' }, status: :unprocessable_entity if new_content.length > 150_000
return render json: { error: 'Only outgoing messages can be edited' }, status: :forbidden unless message.outgoing?
original_content = message.content
# Only save previous_content on first edit to preserve the original message
previous_content_to_save = message.is_edited ? message.previous_content : original_content
message.update!(content: new_content, is_edited: true, previous_content: previous_content_to_save)
edit_message_on_channel(new_content, original_content)
@message = message.reload
end
private
def message
@message ||= @conversation.messages.find(permitted_params[:id])
end
def message_finder
@message_finder ||= MessageFinder.new(@conversation, params)
end
def permitted_params
params.permit(:id, :target_language, :status, :external_error, :content)
end
def already_translated_content_available?
message.translations.present? && message.translations[permitted_params[:target_language]].present?
end
def delete_message_on_channel
return unless @conversation.inbox.channel.respond_to?(:delete_message)
return if message.source_id.blank?
@conversation.inbox.channel.delete_message(message, conversation: @conversation)
rescue StandardError => e
Rails.logger.error "Failed to delete message on channel: #{e.message}"
end
def edit_message_on_channel(new_content, original_content)
return unless @conversation.inbox.channel.respond_to?(:edit_message)
return if message.source_id.blank?
@conversation.inbox.channel.edit_message(message, new_content, conversation: @conversation)
rescue StandardError => e
Rails.logger.error "Failed to edit message on channel: #{e.message}"
was_already_edited = message.previous_content != original_content
if was_already_edited
message.update!(content: original_content)
else
message.update!(content: original_content, is_edited: false, previous_content: nil)
end
raise e
end
# API inbox check
def ensure_api_inbox
# Only API inboxes can update messages
render json: { error: 'Message status update is only allowed for API inboxes' }, status: :forbidden unless @conversation.inbox.api?
end
def trigger_typing_event(event)
user = Current.user || @resource
return unless user.is_a?(User)
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: @conversation, user: user, is_private: params[:private])
end
end