From ab41f7e821193adeeb8b6d8677d5d3e12dce3977 Mon Sep 17 00:00:00 2001 From: Viktor Baranov Date: Thu, 13 Apr 2023 20:39:07 +0300 Subject: [PATCH] Support reCAPTCHA v3 --- .../assets/js/lib/csv_download.js | 182 +++++++++++------- .../lib/block_scout_web/captcha_helper.ex | 20 +- .../address_transaction_controller.ex | 30 ++- .../templates/csv_export/index.html.eex | 18 +- .../templates/layout/app.html.eex | 3 +- .../lib/block_scout_web/views/csv_export.ex | 11 -- apps/block_scout_web/priv/gettext/default.pot | 4 +- .../priv/gettext/en/LC_MESSAGES/default.po | 4 +- ...dress_internal_transaction_csv_exporter.ex | 23 +-- .../address_log_csv_exporter.ex | 20 +- .../address_token_transfer_csv_exporter.ex | 19 +- .../address_transaction_csv_exporter.ex | 20 +- .../lib/explorer/chain/csv_export/helper.ex | 29 +++ ...internal_transaction_csv_exporter_test.exs | 5 +- .../address_log_csv_exporter_test.exs | 2 +- ...dress_token_transfer_csv_exporter_test.exs | 2 +- .../address_transaction_csv_exporter_test.exs | 3 +- config/runtime.exs | 8 +- docker-compose/envs/common-blockscout.env | 2 + docker/Makefile | 6 + 20 files changed, 237 insertions(+), 174 deletions(-) rename apps/explorer/lib/explorer/chain/{ => csv_export}/address_internal_transaction_csv_exporter.ex (84%) rename apps/explorer/lib/explorer/chain/{ => csv_export}/address_log_csv_exporter.ex (81%) rename apps/explorer/lib/explorer/chain/{ => csv_export}/address_token_transfer_csv_exporter.ex (84%) rename apps/explorer/lib/explorer/chain/{ => csv_export}/address_transaction_csv_exporter.ex (88%) create mode 100644 apps/explorer/lib/explorer/chain/csv_export/helper.ex rename apps/explorer/test/explorer/chain/{ => csv_export}/address_internal_transaction_csv_exporter_test.exs (97%) rename apps/explorer/test/explorer/chain/{ => csv_export}/address_log_csv_exporter_test.exs (98%) rename apps/explorer/test/explorer/chain/{ => csv_export}/address_token_transfer_csv_exporter_test.exs (98%) rename apps/explorer/test/explorer/chain/{ => csv_export}/address_transaction_csv_exporter_test.exs (97%) diff --git a/apps/block_scout_web/assets/js/lib/csv_download.js b/apps/block_scout_web/assets/js/lib/csv_download.js index 820f7b6b4e..3b2786eb20 100644 --- a/apps/block_scout_web/assets/js/lib/csv_download.js +++ b/apps/block_scout_web/assets/js/lib/csv_download.js @@ -10,103 +10,141 @@ const DATE_FORMAT = 'YYYY-MM-DD' const $button = $('#export-csv-button') // eslint-disable-next-line -const _instance1 = new Pikaday({ - field: $('.js-datepicker-from')[0], - onSelect: (date) => onSelect(date, 'from_period'), - defaultDate: moment().add(-1, 'months').toDate(), - setDefaultDate: true, - maxDate: new Date(), - format: DATE_FORMAT -}) - +const _instance1 = generateDatePicker('.js-datepicker-from', moment().add(-1, 'months').toDate()) // eslint-disable-next-line -const _instance2 = new Pikaday({ - field: $('.js-datepicker-to')[0], - onSelect: (date) => onSelect(date, 'to_period'), - defaultDate: new Date(), - setDefaultDate: true, - maxDate: new Date(), - format: DATE_FORMAT -}) +const _instance2 = generateDatePicker('.js-datepicker-to', new Date()) + +function generateDatePicker (classSelector, defaultDate) { + return new Pikaday({ + field: $(classSelector)[0], + defaultDate, + setDefaultDate: true, + maxDate: new Date(), + format: DATE_FORMAT + }) +} $button.on('click', () => { // @ts-ignore // eslint-disable-next-line - const recaptchaResponse = grecaptcha.getResponse() - if (recaptchaResponse) { - $button.addClass('spinner') - $button.prop('disabled', true) - const downloadUrl = `${$button.data('link')}&recaptcha_response=${recaptchaResponse}` + const reCaptchaV2ClientKey = document.getElementById('re-captcha-client-key').value + // @ts-ignore + // eslint-disable-next-line + const reCaptchaV3ClientKey = document.getElementById('re-captcha-v3-client-key').value + const addressHash = $button.data('address-hash') + const from = $('.js-datepicker-from').val() + const to = $('.js-datepicker-to').val() + if (reCaptchaV3ClientKey) { + disableBtnWithSpinner() + // @ts-ignore + // eslint-disable-next-line + grecaptcha.ready(function () { + // @ts-ignore + // eslint-disable-next-line + grecaptcha.execute(reCaptchaV3ClientKey, { action: 'login' }) + .then(function (token) { + const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${token}` - $('body').append($('')) - $('#csv-iframe').attr('src', downloadUrl) + download(url) + }) + }) + } else if (reCaptchaV2ClientKey) { + // @ts-ignore + // eslint-disable-next-line + const recaptchaResponse = grecaptcha.getResponse() + if (recaptchaResponse) { + disableBtnWithSpinner() + const url = `${$button.data('link')}?address_id=${addressHash}&from_period=${from}&to_period=${to}&recaptcha_response=${recaptchaResponse}` - const interval = setInterval(handleCSVDownloaded, 1000) - setTimeout(resetDownload, 60000) + download(url, true, true) + } + } else { + alertWhenRecaptchaNotConfigured() + } - function handleCSVDownloaded () { - if (Cookies.get('csv-downloaded') === 'true') { - resetDownload() + function download (url, resetRecaptcha, disable) { + fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json; charset=utf-8' } - } + }) + .then(response => { + if (response.status === 200) { + return response.blob() + } + }) + .then(response => { + if (response) { + const blob = new Blob([response], { type: 'application/csv' }) + const downloadUrl = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = downloadUrl + a.download = `${$button.data('type')}_for_${addressHash}_from_${from}_to_${to}.csv` + document.body.appendChild(a) + a.click() + + resetBtn(resetRecaptcha, disable) + } else { + alertWhenRequestFailed() + resetBtn(resetRecaptcha, disable) + } + }) + } - function resetDownload () { - $button.removeClass('spinner') + function resetBtn (resetRecaptcha, disable) { + $button.removeClass('spinner') + if (!disable) { $button.prop('disabled', false) - clearInterval(interval) - Cookies.remove('csv-downloaded') + } + Cookies.remove('csv-downloaded') + if (resetRecaptcha) { // @ts-ignore // eslint-disable-next-line grecaptcha.reset() } } -}) - -function onSelect (date, paramToReplace) { - const formattedDate = moment(date).format(DATE_FORMAT) - - if (date) { - const csvExportPath = $button.data('link') - const updatedCsvExportUrl = replaceUrlParam(csvExportPath, paramToReplace, formattedDate) - $button.data('link', updatedCsvExportUrl) + function disableBtnWithSpinner () { + $button.addClass('spinner') + disableBtn() } -} -function replaceUrlParam (url, paramName, paramValue) { - if (paramValue == null) { - paramValue = '' - } - const pattern = new RegExp('\\b(' + paramName + '=).*?(&|#|$)') - if (url.search(pattern) >= 0) { - return url.replace(pattern, '$1' + paramValue + '$2') + function disableBtn () { + $button.prop('disabled', true) } - url = url.replace(/[?#]$/, '') - return url + (url.indexOf('?') > 0 ? '&' : '?') + paramName + '=' + paramValue -} +}) const onloadCallback = function () { // @ts-ignore // eslint-disable-next-line const reCaptchaClientKey = document.getElementById('re-captcha-client-key').value - if (reCaptchaClientKey) { - // @ts-ignore - // eslint-disable-next-line - grecaptcha.render('recaptcha', { - sitekey: reCaptchaClientKey, - theme: getThemeMode(), - callback: function () { - // @ts-ignore - document.getElementById('export-csv-button').disabled = false - } - }) - } else { - Swal.fire({ - title: 'Warning', - html: 'CSV download will not work since reCAPTCHA is not configured. Please advise server maintainer to configure RE_CAPTCHA_CLIENT_KEY and RE_CAPTCHA_SECRET_KEY environment variables.', - icon: 'warning' - }) - } + // @ts-ignore + // eslint-disable-next-line + grecaptcha.render('recaptcha', { + sitekey: reCaptchaClientKey, + theme: getThemeMode(), + callback: function () { + // @ts-ignore + document.getElementById('export-csv-button').disabled = false + } + }) +} + +function alertWhenRecaptchaNotConfigured () { + Swal.fire({ + title: 'Warning', + html: 'CSV download is disabled since reCAPTCHA is not configured on server side. Please advise server maintainer to configure RE_CAPTCHA_CLIENT_KEY and RE_CAPTCHA_SECRET_KEY environment variables in case of reCAPTCHAv2 or RE_CAPTCHA_V3_CLIENT_KEY and RE_CAPTCHA_V3_SECRET_KEY environment variables in case of reCAPTCHAv3.', + icon: 'warning' + }) +} + +function alertWhenRequestFailed () { + Swal.fire({ + title: 'Warning', + html: 'Request failed, please try again later or decrease time range for exporting data.', + icon: 'warning' + }) } // @ts-ignore diff --git a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex index abeb3d16eb..a158813b99 100644 --- a/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex +++ b/apps/block_scout_web/lib/block_scout_web/captcha_helper.ex @@ -8,7 +8,9 @@ defmodule BlockScoutWeb.CaptchaHelper do def recaptcha_passed?(nil), do: false def recaptcha_passed?(recaptcha_response) do - re_captcha_secret_key = Application.get_env(:block_scout_web, :re_captcha_secret_key) + re_captcha_v2_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v2_secret_key] + re_captcha_v3_secret_key = Application.get_env(:block_scout_web, :recaptcha)[:v3_secret_key] + re_captcha_secret_key = re_captcha_v2_secret_key || re_captcha_v3_secret_key body = "secret=#{re_captcha_secret_key}&response=#{recaptcha_response}" headers = [{"Content-type", "application/x-www-form-urlencoded"}] @@ -16,7 +18,7 @@ defmodule BlockScoutWeb.CaptchaHelper do case HTTPoison.post("https://www.google.com/recaptcha/api/siteverify", body, headers, []) do {:ok, %HTTPoison.Response{status_code: 200, body: body}} -> case Jason.decode!(body) do - %{"success" => true} -> true + %{"success" => true} = resp -> is_success?(resp) _ -> false end @@ -24,4 +26,18 @@ defmodule BlockScoutWeb.CaptchaHelper do false end end + + defp is_success?(%{"score" => score}) do + check_recaptcha_v3_score(score) + end + + defp is_success?(_resp), do: true + + defp check_recaptcha_v3_score(score) do + if score >= 0.5 do + true + else + false + end + end end diff --git a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex index e22dc29c7b..e60dbc673b 100644 --- a/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex +++ b/apps/block_scout_web/lib/block_scout_web/controllers/address_transaction_controller.ex @@ -14,14 +14,15 @@ defmodule BlockScoutWeb.AddressTransactionController do alias BlockScoutWeb.{AccessHelper, Controller, TransactionView} alias Explorer.{Chain, Market} - alias Explorer.Chain.{ + alias Explorer.Chain.CSVExport.{ AddressInternalTransactionCsvExporter, AddressLogCsvExporter, AddressTokenTransferCsvExporter, - AddressTransactionCsvExporter, - Wei + AddressTransactionCsvExporter } + alias Explorer.Chain.Wei + alias Explorer.ExchangeRates.Token alias Indexer.Fetcher.CoinBalanceOnDemand alias Phoenix.View @@ -171,10 +172,10 @@ defmodule BlockScoutWeb.AddressTransactionController do |> Application.get_env(:captcha_helper) end - defp put_resp_params(conn, file_name) do + defp put_resp_params(conn) do conn |> put_resp_content_type("application/csv") - |> put_resp_header("content-disposition", "attachment; filename=#{file_name}") + |> put_resp_header("content-disposition", "attachment;") |> put_resp_cookie("csv-downloaded", "true", max_age: 86_400, http_only: false) |> send_chunked(200) end @@ -187,8 +188,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "to_period" => to_period, "recaptcha_response" => recaptcha_response }, - csv_export_module, - file_name + csv_export_module ) when is_binary(address_hash_string) do with {:ok, address_hash} <- Chain.string_to_address_hash(address_hash_string), @@ -196,7 +196,7 @@ defmodule BlockScoutWeb.AddressTransactionController do {:recaptcha, true} <- {:recaptcha, captcha_helper().recaptcha_passed?(recaptcha_response)} do address |> csv_export_module.export(from_period, to_period) - |> Enum.reduce_while(put_resp_params(conn, file_name), fn chunk, conn -> + |> Enum.reduce_while(put_resp_params(conn), fn chunk, conn -> case Conn.chunk(conn, chunk) do {:ok, conn} -> {:cont, conn} @@ -217,7 +217,7 @@ defmodule BlockScoutWeb.AddressTransactionController do end end - defp items_csv(conn, _, _, _), do: not_found(conn) + defp items_csv(conn, _, _), do: not_found(conn) def token_transfers_csv(conn, params) do items_csv( @@ -228,8 +228,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "to_period" => params["to_period"], "recaptcha_response" => params["recaptcha_response"] }, - AddressTokenTransferCsvExporter, - "token_transfers.csv" + AddressTokenTransferCsvExporter ) end @@ -247,8 +246,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "to_period" => to_period, "recaptcha_response" => recaptcha_response }, - AddressTransactionCsvExporter, - "transactions.csv" + AddressTransactionCsvExporter ) end @@ -266,8 +264,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "to_period" => to_period, "recaptcha_response" => recaptcha_response }, - AddressInternalTransactionCsvExporter, - "internal_transactions.csv" + AddressInternalTransactionCsvExporter ) end @@ -285,8 +282,7 @@ defmodule BlockScoutWeb.AddressTransactionController do "to_period" => to_period, "recaptcha_response" => recaptcha_response }, - AddressLogCsvExporter, - "logs.csv" + AddressLogCsvExporter ) end end diff --git a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex index bf9b0e1d44..cd98994651 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/csv_export/index.html.eex @@ -21,10 +21,24 @@
- + - + <%= cond do %> + <% Application.get_env(:block_scout_web, :recaptcha)[:v2_client_key] -> %> + + <% Application.get_env(:block_scout_web, :recaptcha)[:v3_client_key] -> %> + + <% true -> %> + <% end %> \ No newline at end of file diff --git a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex index 2c940a7f68..ac77e7d932 100644 --- a/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex +++ b/apps/block_scout_web/lib/block_scout_web/templates/layout/app.html.eex @@ -63,7 +63,8 @@
<%= Application.get_env(:indexer, :first_block) %>
- + + <% show_maintenance_alert = Application.get_env(:block_scout_web, BlockScoutWeb.Chain)[:show_maintenance_alert] %> diff --git a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex index 07b9564594..083e11f53a 100644 --- a/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex +++ b/apps/block_scout_web/lib/block_scout_web/views/csv_export.ex @@ -30,15 +30,4 @@ defmodule BlockScoutWeb.CsvExportView do |> Address.checksum() end end - - defp default_period_start do - DateTime.utc_now() - |> Timex.shift(months: -1) - |> Timex.format!("{YYYY}-{0M}-{0D}") - end - - defp default_period_end do - DateTime.utc_now() - |> Timex.format!("{YYYY}-{0M}-{0D}") - end end diff --git a/apps/block_scout_web/priv/gettext/default.pot b/apps/block_scout_web/priv/gettext/default.pot index e64c8ddcf9..8df086ce3b 100644 --- a/apps/block_scout_web/priv/gettext/default.pot +++ b/apps/block_scout_web/priv/gettext/default.pot @@ -81,7 +81,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:85 +#: lib/block_scout_web/templates/layout/app.html.eex:86 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -1072,7 +1072,7 @@ msgstr "" msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#: lib/block_scout_web/templates/csv_export/index.html.eex:32 #, elixir-autogen, elixir-format msgid "Download" msgstr "" diff --git a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po index 93b21fc21e..75de13315f 100644 --- a/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po +++ b/apps/block_scout_web/priv/gettext/en/LC_MESSAGES/default.po @@ -81,7 +81,7 @@ msgstr "" msgid ") may be added for each contract. Click the Add Library button to add an additional one." msgstr "" -#: lib/block_scout_web/templates/layout/app.html.eex:85 +#: lib/block_scout_web/templates/layout/app.html.eex:86 #, elixir-autogen, elixir-format msgid "- We're indexing this chain right now. Some of the counts may be inaccurate." msgstr "" @@ -1072,7 +1072,7 @@ msgstr "" msgid "Displaying the init data provided of the creating transaction." msgstr "" -#: lib/block_scout_web/templates/csv_export/index.html.eex:24 +#: lib/block_scout_web/templates/csv_export/index.html.eex:32 #, elixir-autogen, elixir-format msgid "Download" msgstr "" diff --git a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex similarity index 84% rename from apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex rename to apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex index 96350b398b..a3872ae8ef 100644 --- a/apps/explorer/lib/explorer/chain/address_internal_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_internal_transaction_csv_exporter.ex @@ -1,27 +1,24 @@ -defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do +defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter do @moduledoc """ Exports internal transactions to a csv file. """ alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, InternalTransaction, Wei} - alias NimbleCSV.RFC4180 + alias Explorer.Chain.CSVExport.Helper - @page_size 150 - - @paging_options %PagingOptions{page_size: @page_size + 1} + @paging_options %PagingOptions{page_size: Helper.page_size() + 1} @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t() def export(address, from_period, to_period) do - from_block = Chain.convert_date_to_min_block(from_period) - to_block = Chain.convert_date_to_max_block(to_period) + {from_block, to_block} = Helper.block_from_period(from_period, to_period) res = address.hash |> fetch_all_internal_transactions(from_block, to_block, @paging_options) |> Enum.sort_by(&{&1.block_number, &1.index, &1.transaction_index}, :desc) |> to_csv_format() - |> dump_to_stream() + |> Helper.dump_to_stream() res end @@ -37,7 +34,7 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do new_acc = internal_transactions ++ acc - case Enum.split(internal_transactions, @page_size) do + case Enum.split(internal_transactions, Helper.page_size()) do {_internal_transactions, [%InternalTransaction{block_number: block_number, transaction_index: transaction_index, index: index}]} -> new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}} @@ -48,14 +45,6 @@ defmodule Explorer.Chain.AddressInternalTransactionCsvExporter do end end - defp dump_to_stream(internal_transactions) do - res = - internal_transactions - |> RFC4180.dump_to_stream() - - res - end - defp to_csv_format(internal_transactions) do row_names = [ "TxHash", diff --git a/apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex similarity index 81% rename from apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex rename to apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex index ed44957ab6..55b339d6cb 100644 --- a/apps/explorer/lib/explorer/chain/address_log_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_log_csv_exporter.ex @@ -1,25 +1,22 @@ -defmodule Explorer.Chain.AddressLogCsvExporter do +defmodule Explorer.Chain.CSVExport.AddressLogCsvExporter do @moduledoc """ Exports internal transactions to a csv file. """ alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, Log, Transaction} - alias NimbleCSV.RFC4180 + alias Explorer.Chain.CSVExport.Helper - @page_size 150 - - @paging_options %PagingOptions{page_size: @page_size + 1} + @paging_options %PagingOptions{page_size: Helper.page_size() + 1} @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t() def export(address, from_period, to_period) do - from_block = Chain.convert_date_to_min_block(from_period) - to_block = Chain.convert_date_to_max_block(to_period) + {from_block, to_block} = Helper.block_from_period(from_period, to_period) address.hash |> fetch_all_logs(from_block, to_block, @paging_options) |> to_csv_format() - |> dump_to_stream() + |> Helper.dump_to_stream() end defp fetch_all_logs(address_hash, from_block, to_block, paging_options, acc \\ []) do @@ -33,7 +30,7 @@ defmodule Explorer.Chain.AddressLogCsvExporter do new_acc = logs ++ acc - case Enum.split(logs, @page_size) do + case Enum.split(logs, Helper.page_size()) do {_logs, [%Log{block_number: block_number, transaction: %Transaction{index: transaction_index}, index: index}]} -> new_paging_options = %{@paging_options | key: {block_number, transaction_index, index}} fetch_all_logs(address_hash, from_block, to_block, new_paging_options, new_acc) @@ -43,11 +40,6 @@ defmodule Explorer.Chain.AddressLogCsvExporter do end end - defp dump_to_stream(logs) do - logs - |> RFC4180.dump_to_stream() - end - defp to_csv_format(logs) do row_names = [ "TxHash", diff --git a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex similarity index 84% rename from apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex rename to apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex index fa8139a412..d2e595d701 100644 --- a/apps/explorer/lib/explorer/chain/address_token_transfer_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_token_transfer_csv_exporter.ex @@ -1,24 +1,22 @@ -defmodule Explorer.Chain.AddressTokenTransferCsvExporter do +defmodule Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter do @moduledoc """ Exports token transfers to a csv file. """ alias Explorer.{Chain, PagingOptions} alias Explorer.Chain.{Address, TokenTransfer} - alias NimbleCSV.RFC4180 + alias Explorer.Chain.CSVExport.Helper - @page_size 150 - @paging_options %PagingOptions{page_size: @page_size + 1, asc_order: true} + @paging_options %PagingOptions{page_size: Helper.page_size() + 1, asc_order: true} @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t() def export(address, from_period, to_period) do - from_block = Chain.convert_date_to_min_block(from_period) - to_block = Chain.convert_date_to_max_block(to_period) + {from_block, to_block} = Helper.block_from_period(from_period, to_period) address.hash |> fetch_all_token_transfers(from_block, to_block, @paging_options) |> to_csv_format(address) - |> dump_to_stream() + |> Helper.dump_to_stream() end def fetch_all_token_transfers(address_hash, from_block, to_block, paging_options, acc \\ []) do @@ -32,7 +30,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do new_acc = acc ++ token_transfers - case Enum.split(token_transfers, @page_size) do + case Enum.split(token_transfers, Helper.page_size()) do {_token_transfers, [%TokenTransfer{block_number: block_number, log_index: log_index}]} -> new_paging_options = %{@paging_options | key: {block_number, log_index}} fetch_all_token_transfers(address_hash, from_block, to_block, new_paging_options, new_acc) @@ -42,11 +40,6 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporter do end end - defp dump_to_stream(transactions) do - transactions - |> RFC4180.dump_to_stream() - end - defp to_csv_format(token_transfers, address) do row_names = [ "TxHash", diff --git a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex similarity index 88% rename from apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex rename to apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex index a68c4aabe0..c76815dc70 100644 --- a/apps/explorer/lib/explorer/chain/address_transaction_csv_exporter.ex +++ b/apps/explorer/lib/explorer/chain/csv_export/address_transaction_csv_exporter.ex @@ -1,4 +1,4 @@ -defmodule Explorer.Chain.AddressTransactionCsvExporter do +defmodule Explorer.Chain.CSVExport.AddressTransactionCsvExporter do @moduledoc """ Exports transactions to a csv file. """ @@ -11,8 +11,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do alias Explorer.{Chain, Market, PagingOptions, Repo} alias Explorer.Market.MarketHistory alias Explorer.Chain.{Address, Transaction, Wei} + alias Explorer.Chain.CSVExport.Helper alias Explorer.ExchangeRates.Token - alias NimbleCSV.RFC4180 @necessity_by_association [ necessity_by_association: %{ @@ -27,20 +27,17 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do } ] - @page_size 150 - - @paging_options %PagingOptions{page_size: @page_size + 1} + @paging_options %PagingOptions{page_size: Helper.page_size() + 1} @spec export(Address.t(), String.t(), String.t()) :: Enumerable.t() def export(address, from_period, to_period) do - from_block = Chain.convert_date_to_min_block(from_period) - to_block = Chain.convert_date_to_max_block(to_period) + {from_block, to_block} = Helper.block_from_period(from_period, to_period) exchange_rate = Market.get_exchange_rate(Explorer.coin()) || Token.null() address.hash |> fetch_all_transactions(from_block, to_block, @paging_options) |> to_csv_format(address, exchange_rate) - |> dump_to_stream() + |> Helper.dump_to_stream() end def fetch_all_transactions(address_hash, from_block, to_block, paging_options, acc \\ []) do @@ -53,7 +50,7 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do transactions = Chain.address_to_transactions_without_rewards(address_hash, options) new_acc = transactions ++ acc - case Enum.split(transactions, @page_size) do + case Enum.split(transactions, Helper.page_size()) do {_transactions, [%Transaction{block_number: block_number, index: index}]} -> new_paging_options = %{@paging_options | key: {block_number, index}} fetch_all_transactions(address_hash, from_block, to_block, new_paging_options, new_acc) @@ -63,11 +60,6 @@ defmodule Explorer.Chain.AddressTransactionCsvExporter do end end - defp dump_to_stream(transactions) do - transactions - |> RFC4180.dump_to_stream() - end - defp to_csv_format(transactions, address, exchange_rate) do row_names = [ "TxHash", diff --git a/apps/explorer/lib/explorer/chain/csv_export/helper.ex b/apps/explorer/lib/explorer/chain/csv_export/helper.ex new file mode 100644 index 0000000000..eec8c85595 --- /dev/null +++ b/apps/explorer/lib/explorer/chain/csv_export/helper.ex @@ -0,0 +1,29 @@ +defmodule Explorer.Chain.CSVExport.Helper do + @moduledoc """ + CSV export helper functions. + """ + + alias Explorer.Chain + alias NimbleCSV.RFC4180 + + @page_size 150 + + def dump_to_stream(items) do + res = + items + |> RFC4180.dump_to_stream() + + res + end + + def page_size do + @page_size + end + + def block_from_period(from_period, to_period) do + from_block = Chain.convert_date_to_min_block(from_period) + to_block = Chain.convert_date_to_max_block(to_period) + + {from_block, to_block} + end +end diff --git a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs similarity index 97% rename from apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs rename to apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs index 34494dc932..be9a2c4cd6 100644 --- a/apps/explorer/test/explorer/chain/address_internal_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_internal_transaction_csv_exporter_test.exs @@ -1,7 +1,8 @@ -defmodule Explorer.Chain.AddressInternalTransactionCsvExporterTest do +defmodule Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporterTest do use Explorer.DataCase - alias Explorer.Chain.{AddressInternalTransactionCsvExporter, Wei} + alias Explorer.Chain.CSVExport.AddressInternalTransactionCsvExporter + alias Explorer.Chain.Wei describe "export/3" do test "exports address internal transactions to csv" do diff --git a/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs similarity index 98% rename from apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs rename to apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs index fe1d0601a8..e7fd8b7ec3 100644 --- a/apps/explorer/test/explorer/chain/address_log_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_log_csv_exporter_test.exs @@ -1,7 +1,7 @@ defmodule Explorer.Chain.AddressLogCsvExporterTest do use Explorer.DataCase - alias Explorer.Chain.AddressLogCsvExporter + alias Explorer.Chain.CSVExport.AddressLogCsvExporter describe "export/3" do test "exports address logs to csv" do diff --git a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs similarity index 98% rename from apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs rename to apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs index c64ecafa99..ca584c19fa 100644 --- a/apps/explorer/test/explorer/chain/address_token_transfer_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_token_transfer_csv_exporter_test.exs @@ -1,7 +1,7 @@ defmodule Explorer.Chain.AddressTokenTransferCsvExporterTest do use Explorer.DataCase - alias Explorer.Chain.AddressTokenTransferCsvExporter + alias Explorer.Chain.CSVExport.AddressTokenTransferCsvExporter describe "export/3" do test "exports token transfers to csv" do diff --git a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs similarity index 97% rename from apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs rename to apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs index 7145e9c507..2035b3465c 100644 --- a/apps/explorer/test/explorer/chain/address_transaction_csv_exporter_test.exs +++ b/apps/explorer/test/explorer/chain/csv_export/address_transaction_csv_exporter_test.exs @@ -1,7 +1,8 @@ defmodule Explorer.Chain.AddressTransactionCsvExporterTest do use Explorer.DataCase - alias Explorer.Chain.{AddressTransactionCsvExporter, Wei} + alias Explorer.Chain.CSVExport.AddressTransactionCsvExporter + alias Explorer.Chain.Wei describe "export/3" do test "exports address transactions to csv" do diff --git a/config/runtime.exs b/config/runtime.exs index 3fc77d3f9b..efd1602d0e 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -23,8 +23,6 @@ config :block_scout_web, dark_forest_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST"), dark_forest_addresses_v_0_5: System.get_env("CUSTOM_CONTRACT_ADDRESSES_DARK_FOREST_V_0_5"), circles_addresses: System.get_env("CUSTOM_CONTRACT_ADDRESSES_CIRCLES"), - re_captcha_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY"), - re_captcha_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY"), new_tags: System.get_env("NEW_TAGS"), chain_id: System.get_env("CHAIN_ID"), json_rpc: System.get_env("JSON_RPC"), @@ -35,6 +33,12 @@ config :block_scout_web, hide_block_miner: ConfigHelper.parse_bool_env_var("HIDE_BLOCK_MINER"), show_tenderly_link: ConfigHelper.parse_bool_env_var("SHOW_TENDERLY_LINK") +config :block_scout_web, :recaptcha, + v2_client_key: System.get_env("RE_CAPTCHA_CLIENT_KEY"), + v2_secret_key: System.get_env("RE_CAPTCHA_SECRET_KEY"), + v3_client_key: System.get_env("RE_CAPTCHA_V3_CLIENT_KEY"), + v3_secret_key: System.get_env("RE_CAPTCHA_V3_SECRET_KEY") + network_path = "NETWORK_PATH" |> System.get_env("/") diff --git a/docker-compose/envs/common-blockscout.env b/docker-compose/envs/common-blockscout.env index b17c9fd817..039e6968da 100644 --- a/docker-compose/envs/common-blockscout.env +++ b/docker-compose/envs/common-blockscout.env @@ -141,6 +141,8 @@ SHOW_TENDERLY_LINK=false TENDERLY_CHAIN_PATH= RE_CAPTCHA_SECRET_KEY= RE_CAPTCHA_CLIENT_KEY= +RE_CAPTCHA_V3_SECRET_KEY= +RE_CAPTCHA_V3_CLIENT_KEY= JSON_RPC= #API_RATE_LIMIT_DISABLED=true API_RATE_LIMIT_TIME_INTERVAL=1s diff --git a/docker/Makefile b/docker/Makefile index dd883a704a..7854471ec2 100644 --- a/docker/Makefile +++ b/docker/Makefile @@ -348,6 +348,12 @@ endif ifdef RE_CAPTCHA_CLIENT_KEY BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_CLIENT_KEY=$(RE_CAPTCHA_CLIENT_KEY)' endif +ifdef RE_CAPTCHA_V3_SECRET_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_SECRET_KEY=$(RE_CAPTCHA_V3_SECRET_KEY)' +endif +ifdef RE_CAPTCHA_V3_CLIENT_KEY + BLOCKSCOUT_CONTAINER_PARAMS += -e 'RE_CAPTCHA_V3_CLIENT_KEY=$(RE_CAPTCHA_V3_CLIENT_KEY)' +endif ifdef CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD BLOCKSCOUT_CONTAINER_PARAMS += -e 'CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD=$(CACHE_ADDRESS_TOKEN_TRANSFERS_COUNTER_PERIOD)' endif