Files
leadchat/app/models/attachment.rb
Sojan Jose 12a82b6459 fix: avoid Slack file upload API for fallback messages (#10461)
Skip calling the Slack file upload API for message types such as
fallback (e.g., Facebook and location messages) that lack actual file
data in attachments. This prevents unnecessary API calls and resolves a
Sentry error currently occurring in production.

fixes: https://github.com/chatwoot/chatwoot/issues/10460
2024-11-26 12:56:40 +08:00

150 lines
4.2 KiB
Ruby

# == Schema Information
#
# Table name: attachments
#
# id :integer not null, primary key
# coordinates_lat :float default(0.0)
# coordinates_long :float default(0.0)
# extension :string
# external_url :string
# fallback_title :string
# file_type :integer default("image")
# created_at :datetime not null
# updated_at :datetime not null
# account_id :integer not null
# message_id :integer not null
#
# Indexes
#
# index_attachments_on_account_id (account_id)
# index_attachments_on_message_id (message_id)
#
class Attachment < ApplicationRecord
include Rails.application.routes.url_helpers
ACCEPTABLE_FILE_TYPES = %w[
text/csv text/plain text/rtf
application/json application/pdf
application/zip application/x-7z-compressed application/vnd.rar application/x-tar
application/msword application/vnd.ms-excel application/vnd.ms-powerpoint application/rtf
application/vnd.oasis.opendocument.text
application/vnd.openxmlformats-officedocument.presentationml.presentation
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
application/vnd.openxmlformats-officedocument.wordprocessingml.document
].freeze
belongs_to :account
belongs_to :message
has_one_attached :file
validate :acceptable_file
validates :external_url, length: { maximum: Limits::URL_LENGTH_LIMIT }
enum file_type: { :image => 0, :audio => 1, :video => 2, :file => 3, :location => 4, :fallback => 5, :share => 6, :story_mention => 7,
:contact => 8, :ig_reel => 9 }
def push_event_data
return unless file_type
return base_data.merge(location_metadata) if file_type.to_sym == :location
return base_data.merge(fallback_data) if file_type.to_sym == :fallback
return base_data.merge(contact_metadata) if file_type.to_sym == :contact
base_data.merge(file_metadata)
end
# NOTE: the URl returned does a 301 redirect to the actual file
def file_url
file.attached? ? url_for(file) : ''
end
# NOTE: for External services use this methods since redirect doesn't work effectively in a lot of cases
def download_url
ActiveStorage::Current.url_options = Rails.application.routes.default_url_options if ActiveStorage::Current.url_options.blank?
file.attached? ? file.blob.url : ''
end
def thumb_url
if file.attached? && file.representable?
url_for(file.representation(resize_to_fill: [250, nil]))
else
''
end
end
def with_attached_file?
[:image, :audio, :video, :file].include?(file_type.to_sym)
end
private
def file_metadata
metadata = {
extension: extension,
data_url: file_url,
thumb_url: thumb_url,
file_size: file.byte_size,
width: file.metadata[:width],
height: file.metadata[:height]
}
metadata[:data_url] = metadata[:thumb_url] = external_url if message.inbox.instagram? && message.incoming?
metadata
end
def location_metadata
{
coordinates_lat: coordinates_lat,
coordinates_long: coordinates_long,
fallback_title: fallback_title,
data_url: external_url
}
end
def fallback_data
{
fallback_title: fallback_title,
data_url: external_url
}
end
def base_data
{
id: id,
message_id: message_id,
file_type: file_type,
account_id: account_id
}
end
def contact_metadata
{
fallback_title: fallback_title
}
end
def should_validate_file?
return unless file.attached?
# we are only limiting attachment types in case of website widget
return unless message.inbox.channel_type == 'Channel::WebWidget'
true
end
def acceptable_file
return unless should_validate_file?
validate_file_size(file.byte_size)
validate_file_content_type(file.content_type)
end
def validate_file_content_type(file_content_type)
errors.add(:file, 'type not supported') unless media_file?(file_content_type) || ACCEPTABLE_FILE_TYPES.include?(file_content_type)
end
def validate_file_size(byte_size)
errors.add(:file, 'size is too big') if byte_size > 40.megabytes
end
def media_file?(file_content_type)
file_content_type.start_with?('image/', 'video/', 'audio/')
end
end