chore: Add down gem for Local file downloads (#2765)
- Add down gem to handle downloading files to host machine - Remove the LocalResource class - Introduce max limit for contact avatars send via SDK
This commit is contained in:
parent
29f54c1f26
commit
8daf1fe033
11 changed files with 47 additions and 93 deletions
2
Gemfile
2
Gemfile
|
@ -35,6 +35,8 @@ gem 'commonmarker'
|
||||||
gem 'json_schemer'
|
gem 'json_schemer'
|
||||||
# Rack middleware for blocking & throttling abusive requests
|
# Rack middleware for blocking & throttling abusive requests
|
||||||
gem 'rack-attack'
|
gem 'rack-attack'
|
||||||
|
# a utility tool for streaming, flexible and safe downloading of remote files
|
||||||
|
gem 'down', '~> 5.0'
|
||||||
|
|
||||||
##-- for active storage --##
|
##-- for active storage --##
|
||||||
gem 'aws-sdk-s3', require: false
|
gem 'aws-sdk-s3', require: false
|
||||||
|
|
|
@ -177,6 +177,8 @@ GEM
|
||||||
dotenv-rails (2.7.6)
|
dotenv-rails (2.7.6)
|
||||||
dotenv (= 2.7.6)
|
dotenv (= 2.7.6)
|
||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
|
down (5.2.3)
|
||||||
|
addressable (~> 2.8)
|
||||||
dry-inflector (0.2.1)
|
dry-inflector (0.2.1)
|
||||||
ecma-re-validator (0.3.0)
|
ecma-re-validator (0.3.0)
|
||||||
regexp_parser (~> 2.0)
|
regexp_parser (~> 2.0)
|
||||||
|
@ -651,6 +653,7 @@ DEPENDENCIES
|
||||||
devise-secure_password (~> 2.0)
|
devise-secure_password (~> 2.0)
|
||||||
devise_token_auth
|
devise_token_auth
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
|
down (~> 5.0)
|
||||||
facebook-messenger
|
facebook-messenger
|
||||||
factory_bot_rails
|
factory_bot_rails
|
||||||
faker
|
faker
|
||||||
|
|
|
@ -24,6 +24,7 @@ class Messages::Facebook::MessageBuilder
|
||||||
build_contact
|
build_contact
|
||||||
build_message
|
build_message
|
||||||
end
|
end
|
||||||
|
ensure_contact_avatar
|
||||||
rescue Koala::Facebook::AuthenticationError
|
rescue Koala::Facebook::AuthenticationError
|
||||||
Rails.logger.info "Facebook Authorization expired for Inbox #{@inbox.id}"
|
Rails.logger.info "Facebook Authorization expired for Inbox #{@inbox.id}"
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
|
@ -41,7 +42,6 @@ class Messages::Facebook::MessageBuilder
|
||||||
return if contact.present?
|
return if contact.present?
|
||||||
|
|
||||||
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
@contact = Contact.create!(contact_params.except(:remote_avatar_url))
|
||||||
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url]) if contact_params[:remote_avatar_url]
|
|
||||||
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
@contact_inbox = ContactInbox.create(contact: contact, inbox: @inbox, source_id: @sender_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -61,10 +61,21 @@ class Messages::Facebook::MessageBuilder
|
||||||
end
|
end
|
||||||
|
|
||||||
def attach_file(attachment, file_url)
|
def attach_file(attachment, file_url)
|
||||||
file_resource = LocalResource.new(file_url)
|
attachment_file = Down.download(
|
||||||
attachment.file.attach(io: file_resource.file, filename: file_resource.filename, content_type: file_resource.encoding)
|
file_url
|
||||||
rescue *ExceptionList::URI_EXCEPTIONS => e
|
)
|
||||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
attachment.file.attach(
|
||||||
|
io: attachment_file,
|
||||||
|
filename: attachment_file.original_filename,
|
||||||
|
content_type: attachment_file.content_type
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_contact_avatar
|
||||||
|
return if contact_params[:remote_avatar_url].blank?
|
||||||
|
return if @contact.avatar.attached?
|
||||||
|
|
||||||
|
ContactAvatarJob.perform_later(@contact, contact_params[:remote_avatar_url])
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
|
|
|
@ -75,33 +75,9 @@ class Api::V1::Accounts::CallbacksController < Api::V1::Accounts::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_avatar(facebook_inbox, page_id)
|
def set_avatar(facebook_inbox, page_id)
|
||||||
uri = get_avatar_url(page_id)
|
avatar_file = Down.download(
|
||||||
|
"http://graph.facebook.com/#{page_id}/picture?type=large"
|
||||||
return unless uri
|
)
|
||||||
|
facebook_inbox.avatar.attach(io: avatar_file, filename: avatar_file.original_filename, content_type: avatar_file.content_type)
|
||||||
avatar_resource = LocalResource.new(uri)
|
|
||||||
facebook_inbox.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
|
||||||
rescue *ExceptionList::URI_EXCEPTIONS => e
|
|
||||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_avatar_url(page_id)
|
|
||||||
begin
|
|
||||||
url = 'http://graph.facebook.com/' << page_id << '/picture?type=large'
|
|
||||||
uri = URI.parse(url)
|
|
||||||
tries = 3
|
|
||||||
begin
|
|
||||||
response = uri.open(redirect: false)
|
|
||||||
rescue OpenURI::HTTPRedirect => e
|
|
||||||
uri = e.uri # assigned from the "Location" response header
|
|
||||||
retry if (tries -= 1).positive?
|
|
||||||
raise
|
|
||||||
end
|
|
||||||
pic_url = response.base_uri.to_s
|
|
||||||
rescue StandardError => e
|
|
||||||
Rails.logger.debug { "Rescued: #{e.inspect}" }
|
|
||||||
pic_url = nil
|
|
||||||
end
|
|
||||||
pic_url
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,9 +2,12 @@ class ContactAvatarJob < ApplicationJob
|
||||||
queue_as :default
|
queue_as :default
|
||||||
|
|
||||||
def perform(contact, avatar_url)
|
def perform(contact, avatar_url)
|
||||||
avatar_resource = LocalResource.new(avatar_url)
|
avatar_file = Down.download(
|
||||||
contact.avatar.attach(io: avatar_resource.file, filename: avatar_resource.tmp_filename, content_type: avatar_resource.encoding)
|
avatar_url,
|
||||||
rescue *ExceptionList::URI_EXCEPTIONS, NoMethodError => e
|
max_size: 15 * 1024 * 1024
|
||||||
|
)
|
||||||
|
contact.avatar.attach(io: avatar_file, filename: avatar_file.original_filename, content_type: avatar_file.content_type)
|
||||||
|
rescue Down::Error => e
|
||||||
Rails.logger.info "Exception: invalid avatar url #{avatar_url} : #{e.message}"
|
Rails.logger.info "Exception: invalid avatar url #{avatar_url} : #{e.message}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,8 +45,10 @@ class ContactIpLookupJob < ApplicationJob
|
||||||
|
|
||||||
def setup_vendor_db
|
def setup_vendor_db
|
||||||
base_url = 'https://download.maxmind.com/app/geoip_download'
|
base_url = 'https://download.maxmind.com/app/geoip_download'
|
||||||
source = URI.parse("#{base_url}?edition_id=GeoLite2-City&suffix=tar.gz&license_key=#{ENV['IP_LOOKUP_API_KEY']}").open
|
source_file = Down.download(
|
||||||
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(source))
|
"#{base_url}?edition_id=GeoLite2-City&suffix=tar.gz&license_key=#{ENV['IP_LOOKUP_API_KEY']}"
|
||||||
|
)
|
||||||
|
tar_extract = Gem::Package::TarReader.new(Zlib::GzipReader.open(source_file))
|
||||||
tar_extract.rewind
|
tar_extract.rewind
|
||||||
|
|
||||||
tar_extract.each do |entry|
|
tar_extract.each do |entry|
|
||||||
|
|
|
@ -93,7 +93,9 @@ class Twilio::IncomingMessageService
|
||||||
def attach_files
|
def attach_files
|
||||||
return if params[:MediaUrl0].blank?
|
return if params[:MediaUrl0].blank?
|
||||||
|
|
||||||
file_resource = LocalResource.new(params[:MediaUrl0], params[:MediaContentType0])
|
attachment_file = Down.download(
|
||||||
|
params[:MediaUrl0]
|
||||||
|
)
|
||||||
|
|
||||||
attachment = @message.attachments.new(
|
attachment = @message.attachments.new(
|
||||||
account_id: @message.account_id,
|
account_id: @message.account_id,
|
||||||
|
@ -101,13 +103,11 @@ class Twilio::IncomingMessageService
|
||||||
)
|
)
|
||||||
|
|
||||||
attachment.file.attach(
|
attachment.file.attach(
|
||||||
io: file_resource.file,
|
io: attachment_file,
|
||||||
filename: file_resource.tmp_filename,
|
filename: attachment_file.original_filename,
|
||||||
content_type: file_resource.encoding
|
content_type: attachment_file.content_type
|
||||||
)
|
)
|
||||||
|
|
||||||
@message.save!
|
@message.save!
|
||||||
rescue *ExceptionList::URI_EXCEPTIONS => e
|
|
||||||
Rails.logger.info "invalid url #{file_url} : #{e.message}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,7 @@ class ChatwootHub
|
||||||
info = info.merge(instance_metrics) unless ENV['DISABLE_TELEMETRY']
|
info = info.merge(instance_metrics) unless ENV['DISABLE_TELEMETRY']
|
||||||
response = RestClient.post(PING_URL, info.to_json, { content_type: :json, accept: :json })
|
response = RestClient.post(PING_URL, info.to_json, { content_type: :json, accept: :json })
|
||||||
version = JSON.parse(response)['version']
|
version = JSON.parse(response)['version']
|
||||||
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, *ExceptionList::URI_EXCEPTIONS => e
|
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
|
||||||
Rails.logger.info "Exception: #{e.message}"
|
Rails.logger.info "Exception: #{e.message}"
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Sentry.capture_exception(e)
|
Sentry.capture_exception(e)
|
||||||
|
@ -47,7 +47,7 @@ class ChatwootHub
|
||||||
def self.register_instance(company_name, owner_name, owner_email)
|
def self.register_instance(company_name, owner_name, owner_email)
|
||||||
info = { company_name: company_name, owner_name: owner_name, owner_email: owner_email, subscribed_to_mailers: true }
|
info = { company_name: company_name, owner_name: owner_name, owner_email: owner_email, subscribed_to_mailers: true }
|
||||||
RestClient.post(REGISTRATION_URL, info.merge(instance_config).to_json, { content_type: :json, accept: :json })
|
RestClient.post(REGISTRATION_URL, info.merge(instance_config).to_json, { content_type: :json, accept: :json })
|
||||||
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, *ExceptionList::URI_EXCEPTIONS => e
|
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
|
||||||
Rails.logger.info "Exception: #{e.message}"
|
Rails.logger.info "Exception: #{e.message}"
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Raven.capture_exception(e)
|
Raven.capture_exception(e)
|
||||||
|
@ -58,7 +58,7 @@ class ChatwootHub
|
||||||
|
|
||||||
info = { event_name: event_name, event_data: event_data }
|
info = { event_name: event_name, event_data: event_data }
|
||||||
RestClient.post(EVENTS_URL, info.merge(instance_config).to_json, { content_type: :json, accept: :json })
|
RestClient.post(EVENTS_URL, info.merge(instance_config).to_json, { content_type: :json, accept: :json })
|
||||||
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, *ExceptionList::URI_EXCEPTIONS => e
|
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
|
||||||
Rails.logger.info "Exception: #{e.message}"
|
Rails.logger.info "Exception: #{e.message}"
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Sentry.capture_exception(e)
|
Sentry.capture_exception(e)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module ExceptionList
|
module ExceptionList
|
||||||
URI_EXCEPTIONS = [Errno::ETIMEDOUT, Errno::ECONNREFUSED, URI::InvalidURIError, Net::OpenTimeout, SocketError].freeze
|
|
||||||
REST_CLIENT_EXCEPTIONS = [RestClient::NotFound, RestClient::GatewayTimeout, RestClient::BadRequest,
|
REST_CLIENT_EXCEPTIONS = [RestClient::NotFound, RestClient::GatewayTimeout, RestClient::BadRequest,
|
||||||
RestClient::MethodNotAllowed, RestClient::Forbidden, RestClient::InternalServerError, RestClient::PayloadTooLarge].freeze
|
RestClient::MethodNotAllowed, RestClient::Forbidden, RestClient::InternalServerError,
|
||||||
|
RestClient::PayloadTooLarge, SocketError].freeze
|
||||||
SMTP_EXCEPTIONS = [
|
SMTP_EXCEPTIONS = [
|
||||||
Net::SMTPSyntaxError
|
Net::SMTPSyntaxError
|
||||||
].freeze
|
].freeze
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
class LocalResource
|
|
||||||
attr_reader :uri
|
|
||||||
|
|
||||||
def initialize(uri, file_type = nil)
|
|
||||||
@uri = URI(uri)
|
|
||||||
@file_type = file_type
|
|
||||||
end
|
|
||||||
|
|
||||||
def file
|
|
||||||
@file ||= Tempfile.new(tmp_filename, tmp_folder, encoding: encoding).tap do |f|
|
|
||||||
io.rewind
|
|
||||||
f.write(io.read)
|
|
||||||
f.close
|
|
||||||
end
|
|
||||||
@file.open
|
|
||||||
end
|
|
||||||
|
|
||||||
def io
|
|
||||||
# TODO: should we use RestClient here too ?
|
|
||||||
@io ||= uri.open(read_timeout: 5)
|
|
||||||
end
|
|
||||||
|
|
||||||
def encoding
|
|
||||||
io.rewind
|
|
||||||
io.read.encoding
|
|
||||||
end
|
|
||||||
|
|
||||||
def find_file_type
|
|
||||||
@file_type ? @file_type.split('/').last : Pathname.new(uri.path).extname
|
|
||||||
end
|
|
||||||
|
|
||||||
def tmp_filename
|
|
||||||
[Time.now.to_i.to_s, find_file_type].join('.')
|
|
||||||
end
|
|
||||||
|
|
||||||
def tmp_folder
|
|
||||||
Rails.root.join('tmp')
|
|
||||||
end
|
|
||||||
|
|
||||||
def filename
|
|
||||||
File.basename(uri.path)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -6,7 +6,7 @@ class Webhooks::Trigger
|
||||||
headers: { content_type: :json, accept: :json },
|
headers: { content_type: :json, accept: :json },
|
||||||
timeout: 5
|
timeout: 5
|
||||||
)
|
)
|
||||||
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS, *ExceptionList::URI_EXCEPTIONS => e
|
rescue *ExceptionList::REST_CLIENT_EXCEPTIONS => e
|
||||||
Rails.logger.info "Exception: invalid webhook url #{url} : #{e.message}"
|
Rails.logger.info "Exception: invalid webhook url #{url} : #{e.message}"
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Sentry.capture_exception(e)
|
Sentry.capture_exception(e)
|
||||||
|
|
Loading…
Reference in a new issue