Atomically reserve Chatwoot Cloud account email capacity before sending new agent invitations, including bulk creation, and roll back with HTTP 429 when the limit is exhausted.
90 lines
2.6 KiB
Ruby
90 lines
2.6 KiB
Ruby
module RequestExceptionHandler
|
|
extend ActiveSupport::Concern
|
|
|
|
QUERY_CANCELED_ERROR_MESSAGE_PATTERNS = [
|
|
'ActiveRecord::QueryCanceled',
|
|
'PG::QueryCanceled',
|
|
'canceling statement due to statement timeout'
|
|
].freeze
|
|
|
|
included do
|
|
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
|
rescue_from CustomExceptions::Inbox::LimitExceeded,
|
|
CustomExceptions::Account::EmailLimitExceeded,
|
|
with: :render_error_response
|
|
end
|
|
|
|
private
|
|
|
|
def handle_with_exception
|
|
yield
|
|
rescue ActiveRecord::RecordNotFound => e
|
|
log_handled_error(e)
|
|
render_not_found_error('Resource could not be found')
|
|
rescue Pundit::NotAuthorizedError => e
|
|
log_handled_error(e)
|
|
render_unauthorized('You are not authorized to do this action')
|
|
rescue ActionController::ParameterMissing => e
|
|
log_handled_error(e)
|
|
render_could_not_create_error(e.message)
|
|
rescue ActiveRecord::QueryCanceled => e
|
|
log_handled_error(e)
|
|
render_could_not_create_error(database_query_canceled_message)
|
|
ensure
|
|
# to address the thread variable leak issues in Puma/Thin webserver
|
|
Current.reset
|
|
end
|
|
|
|
def render_unauthorized(message)
|
|
render json: { error: message }, status: :unauthorized
|
|
end
|
|
|
|
def render_not_found_error(message)
|
|
render json: { error: message }, status: :not_found
|
|
end
|
|
|
|
def render_could_not_create_error(error)
|
|
render json: { error: sanitized_error_message(error) }, status: :unprocessable_entity
|
|
end
|
|
|
|
def render_payment_required(message)
|
|
render json: { error: message }, status: :payment_required
|
|
end
|
|
|
|
def render_internal_server_error(message)
|
|
render json: { error: message }, status: :internal_server_error
|
|
end
|
|
|
|
def render_record_invalid(exception)
|
|
log_handled_error(exception)
|
|
render json: {
|
|
message: exception.record.errors.full_messages.join(', '),
|
|
attributes: exception.record.errors.attribute_names
|
|
}, status: :unprocessable_entity
|
|
end
|
|
|
|
def render_error_response(exception)
|
|
log_handled_error(exception)
|
|
render json: exception.to_hash, status: exception.http_status
|
|
end
|
|
|
|
def log_handled_error(exception)
|
|
logger.info("Handled error: #{exception.inspect}")
|
|
end
|
|
|
|
def sanitized_error_message(message)
|
|
return database_query_canceled_message if database_query_canceled_message?(message)
|
|
|
|
message
|
|
end
|
|
|
|
def database_query_canceled_message?(message)
|
|
error_message = message.to_s
|
|
QUERY_CANCELED_ERROR_MESSAGE_PATTERNS.any? { |pattern| error_message.include?(pattern) }
|
|
end
|
|
|
|
def database_query_canceled_message
|
|
I18n.t('errors.database.query_canceled')
|
|
end
|
|
end
|