diff --git a/services/app/apps/codebattle/lib/codebattle/application.ex b/services/app/apps/codebattle/lib/codebattle/application.ex
index fbca272b5..0a76f96af 100644
--- a/services/app/apps/codebattle/lib/codebattle/application.ex
+++ b/services/app/apps/codebattle/lib/codebattle/application.ex
@@ -39,6 +39,7 @@ defmodule Codebattle.Application do
children =
[
{ChromicPDF, chromic_pdf_opts()},
+ {Codebattle.ImageCache, []},
{Codebattle.Repo, []},
{Registry, keys: :unique, name: Codebattle.Registry},
CodebattleWeb.Telemetry,
diff --git a/services/app/apps/codebattle/lib/codebattle/image_cache.ex b/services/app/apps/codebattle/lib/codebattle/image_cache.ex
new file mode 100644
index 000000000..a4a26ba92
--- /dev/null
+++ b/services/app/apps/codebattle/lib/codebattle/image_cache.ex
@@ -0,0 +1,59 @@
+defmodule Codebattle.ImageCache do
+ @moduledoc false
+ use GenServer
+
+ # 2 hours in milliseconds
+ @cleanup_interval 2 * 60 * 60 * 1000
+
+ def start_link(_opts) do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def init(:ok) do
+ create_table()
+ schedule_cleanup()
+ {:ok, %{}}
+ end
+
+ def create_table do
+ :ets.new(
+ :html_images,
+ [
+ :set,
+ :public,
+ :named_table,
+ {:write_concurrency, true},
+ {:read_concurrency, true}
+ ]
+ )
+ end
+
+ def put_image(cache_key, image) do
+ :ets.insert(:html_images, {cache_key, image})
+ :ok
+ end
+
+ def get_image(cache_key) do
+ case :ets.lookup(:html_images, cache_key) do
+ [{^cache_key, cached_image}] ->
+ cached_image
+
+ [] ->
+ nil
+ end
+ end
+
+ def clean_table do
+ :ets.delete_all_objects(:html_images)
+ end
+
+ def handle_info(:cleanup, state) do
+ clean_table()
+ schedule_cleanup()
+ {:noreply, state}
+ end
+
+ defp schedule_cleanup do
+ Process.send_after(self(), :cleanup, @cleanup_interval)
+ end
+end
diff --git a/services/app/apps/codebattle/lib/codebattle_web/controllers/game/image_controller.ex b/services/app/apps/codebattle/lib/codebattle_web/controllers/game/image_controller.ex
index 78ef54b08..8cc39278d 100644
--- a/services/app/apps/codebattle/lib/codebattle_web/controllers/game/image_controller.ex
+++ b/services/app/apps/codebattle/lib/codebattle_web/controllers/game/image_controller.ex
@@ -1,83 +1,159 @@
defmodule CodebattleWeb.Game.ImageController do
use CodebattleWeb, :controller
+ use Gettext, backend: CodebattleWeb.Gettext
alias Codebattle.Game.Context
-
- @fake_html_to_image Application.compile_env(:codebattle, :fake_html_to_image, false)
+ alias Codebattle.Game.Player
+ alias CodebattleWeb.HtmlImage
def show(conn, %{"game_id" => id}) do
case Context.fetch_game(id) do
{:ok, game} ->
- {:ok, image} =
- game
- |> prepare_image_html()
- |> generate_png()
-
- conn
- |> put_resp_content_type("image/png")
- |> send_resp(200, image)
+ cache_key = "g_#{id}_#{game.state}"
+ html = prepare_image_html(game)
+ HtmlImage.render_image(conn, cache_key, html)
- {:error, reason} ->
- conn
- |> put_status(:not_found)
- |> json(%{error: inspect(reason)})
+ {:error, _reason} ->
+ send_resp(conn, :ok, "")
end
end
defp prepare_image_html(game) do
"""
-
-
-
-
- @#{player.name} (#{player.lang}) - #{player.rating}
-
+
+
})
+
@#{player.name}(#{player.rating}) - #{player.lang}
+
#{result}
"""
end
-
- defp generate_png(html_content) do
- if @fake_html_to_image do
- {:ok, html_content}
- else
- ChromicPDF.capture_screenshot({:html, html_content}, capture_screenshot: %{format: "png"})
- end
- end
end
diff --git a/services/app/apps/codebattle/lib/codebattle_web/controllers/game_controller.ex b/services/app/apps/codebattle/lib/codebattle_web/controllers/game_controller.ex
index 926234110..d27ab7c66 100644
--- a/services/app/apps/codebattle/lib/codebattle_web/controllers/game_controller.ex
+++ b/services/app/apps/codebattle/lib/codebattle_web/controllers/game_controller.ex
@@ -39,37 +39,18 @@ defmodule CodebattleWeb.GameController do
case {game.state, is_player} do
{"waiting_opponent", false} ->
- player = Helpers.get_first_player(game)
-
conn
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Join game",
- description: "Game against #{player_info(player, game)}",
- url: Routes.game_url(conn, :show, id, level: Helpers.get_level(game)),
- image: Routes.game_image_url(conn, :show, id),
- twitter: get_twitter_labels_meta([player])
- })
+ |> put_game_meta_tags(game)
|> render("join.html", %{game: game, user: user})
_ ->
- first = Helpers.get_first_player(game)
- second = Helpers.get_second_player(game)
-
conn
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Cool game",
- description: "#{player_info(first, game)} vs #{player_info(second, game)}",
- url: Routes.game_url(conn, :show, id),
- image: Routes.game_image_url(conn, :show, id),
- twitter: get_twitter_labels_meta([first, second])
- })
+ |> put_game_meta_tags(game)
|> render("show.html", %{game: game, user: user})
end
game ->
- if Playbook.Context.exists?(game.id) && can_see_game(user, game) do
- [first, second] = get_users(game)
-
+ if Playbook.Context.exists?(game.id) && can_see_history_game?(user, game) do
score = Context.fetch_score_by_game_id(game.id)
game_params =
@@ -87,22 +68,11 @@ defmodule CodebattleWeb.GameController do
langs: Languages.get_langs(),
players: present_users_for_gon(game.users)
)
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Cool archived game",
- description: "#{user_info(first)} vs #{user_info(second)}",
- url: Routes.game_url(conn, :show, id),
- image: Routes.game_image_url(conn, :show, id),
- twitter: get_twitter_labels_meta(game.users)
- })
+ |> put_game_meta_tags(game)
|> render("show.html", %{game: game, user: user})
else
conn
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Game Result",
- description: "Game is over",
- image: Routes.game_image_url(conn, :show, id),
- url: Routes.game_url(conn, :show, id)
- })
+ |> put_game_meta_tags(game)
|> render("game_result.html", %{game: game, user: user})
end
end
@@ -138,37 +108,6 @@ defmodule CodebattleWeb.GameController do
end
end
- defp user_info(user), do: "@#{user.name}(#{user.lang})-#{user.rating}"
-
- defp player_info(nil, _game), do: ""
-
- defp player_info(player, game) do
- "@#{player.name}(#{player.lang})-#{player.rating} level:#{Helpers.get_level(game)}"
- end
-
- defp get_twitter_labels_meta(players) do
- players
- |> Enum.with_index(1)
- |> Enum.reduce(%{}, fn
- {nil, _i}, acc ->
- acc
-
- {player, i}, acc ->
- label = player.name
- data = "#{player.rating} - #{player.lang}"
-
- acc |> Map.put("label#{i}", label) |> Map.put("data#{i}", data)
- end)
- end
-
- defp get_users(game) do
- case Enum.count(game.users) do
- 0 -> [User.build_guest(), User.build_guest()]
- 1 -> game.users ++ [User.build_guest()]
- _ -> game.users
- end
- end
-
defp present_users_for_gon(users) do
Enum.map(
users,
@@ -186,14 +125,14 @@ defmodule CodebattleWeb.GameController do
)
end
- defp can_see_game(%{subscription_type: :admin}, _game), do: true
+ defp can_see_history_game?(%{subscription_type: :admin}, _game), do: true
- defp can_see_game(%{subscription_type: :premium} = user, game) do
- [first, second] = get_users(game)
- user.id == first.id || user.id == second.id
+ # defp can_see_game?(%{subscription_type: :premium} = user, game) do
+ defp can_see_history_game?(user, game) do
+ Enum.any?(game.players, &(&1.id == user.id))
end
- defp can_see_game(_user, _game), do: false
+ # defp can_see_history_game?(_user, _game), do: false
defp maybe_get_reports(user, game_id) do
if User.admin?(user) do
@@ -206,4 +145,53 @@ defmodule CodebattleWeb.GameController do
defp jitsi_api_key do
Application.get_env(:codebattle, :jitsi_api_key)
end
+
+ defp put_game_meta_tags(conn, game) do
+ put_meta_tags(conn, %{
+ title: Application.get_env(:codebattle, :app_title),
+ description: game_meta_description(game),
+ url: Routes.game_url(conn, :show, game.id, level: Helpers.get_level(game)),
+ image: Routes.game_image_url(conn, :show, game.id),
+ twitter: get_twitter_labels_meta(game.players)
+ })
+ end
+
+ defp get_twitter_labels_meta(players) do
+ players
+ |> Enum.with_index(1)
+ |> Enum.reduce(%{}, fn
+ {nil, _i}, acc ->
+ acc
+
+ {player, i}, acc ->
+ label = player.name
+ data = "#{player.rating} - #{player.lang}"
+
+ acc |> Map.put("label#{i}", label) |> Map.put("data#{i}", data)
+ end)
+ end
+
+ defp game_meta_description(%{state: "waiting_opponent", players: [player]} = game) do
+ level = Gettext.gettext(CodebattleWeb.Gettext, "Level: #{game.level}")
+
+ gettext("Play with") <>
+ ": " <>
+ "@#{player.name}(#{player.rating})-#{player.lang}" <>
+ ". " <>
+ gettext("Waiting for an opponent") <> ". " <> level
+ end
+
+ defp game_meta_description(%{players: [player1, player2]} = game) do
+ level = Gettext.gettext(CodebattleWeb.Gettext, "Level: #{game.level}")
+ state = Gettext.gettext(CodebattleWeb.Gettext, "Game state: #{game.state}")
+
+ gettext("Game between") <>
+ ": " <>
+ "@#{player1.name}(#{player1.rating})-#{player1.lang}" <>
+ " VS " <>
+ "@#{player2.name}(#{player2.rating})-#{player2.lang}" <>
+ ". " <> level <> ". " <> state
+ end
+
+ defp game_meta_description(_game), do: "Unknown game"
end
diff --git a/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament/image_controller.ex b/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament/image_controller.ex
index a23e02ac9..0b93bb3ff 100644
--- a/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament/image_controller.ex
+++ b/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament/image_controller.ex
@@ -3,24 +3,21 @@ defmodule CodebattleWeb.Tournament.ImageController do
use Gettext, backend: CodebattleWeb.Gettext
alias Codebattle.Tournament
+ alias CodebattleWeb.HtmlImage
def show(conn, %{"id" => id}) do
- # TODO: add ETS cache for image
case Tournament.Context.get(id) do
nil ->
send_resp(conn, :ok, "")
tournament ->
- html_content = render_image(tournament)
- {:ok, image} = generate_png(html_content)
-
- conn
- |> put_resp_content_type("image/png")
- |> send_resp(200, Base.decode64!(image))
+ cache_key = "t_#{id}_#{tournament.state}"
+ html = prepare_image_html(tournament)
+ HtmlImage.render_image(conn, cache_key, html)
end
end
- defp render_image(tournament) do
+ defp prepare_image_html(tournament) do
"""
@@ -29,14 +26,16 @@ defmodule CodebattleWeb.Tournament.ImageController do
html, body {
margin: 0;
padding: 0;
- /* Let them expand to the “browser” size (which we'll define via ChromicPDF) */
width: 100%;
height: 100%;
background: #f5f7fa;
- font-family: 'Helvetica Neue', Arial, sans-serif; /* Change to desired font */
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ }
+ p {
+ margin: 5px;
+ padding: 5px;
}
.card {
- /* Fill the entire viewport */
width: 100%;
height: 100%;
background: #ffffff;
@@ -47,14 +46,14 @@ defmodule CodebattleWeb.Tournament.ImageController do
flex-direction: column;
}
.header {
- background: linear-gradient(135deg, #667eea, #764ba2);
- background: #000; /* Dark background */
- font-family: 'Helvetica Neue', Arial, sans-serif; /* Change to desired font */
- color: #fff; /* White text */
+ background: #000;
+ font-family: 'Helvetica Neue', Arial, sans-serif;
+ color: #fff;
text-align: center;
}
.header img {
- width: 60px;
+ width: 100px;
+ margin-top: 20px;
height: auto;
}
.content {
@@ -68,24 +67,33 @@ defmodule CodebattleWeb.Tournament.ImageController do
}
.footer {
text-align: center;
- font-size: 10px;
- color: #aaa;
- padding: 5px;
- background: #f0f0f0;
+ font-size: 12px;
+ color: #fff;
+ padding: 8px;
+ background: #000;
}
#{render_content(tournament)}
@@ -93,26 +101,14 @@ defmodule CodebattleWeb.Tournament.ImageController do
"""
end
- defp logo_url do
- if logo = Application.get_env(:codebattle, :collab_logo) do
- logo
- else
- "https://codebattle.hexlet.io/assets/images/logo.svg"
- end
- end
-
defp render_content(tournament) do
- type = to_string(tournament.type)
- state = to_string(tournament.state)
+ type = Gettext.gettext(CodebattleWeb.Gettext, to_string(tournament.type))
+ state = Gettext.gettext(CodebattleWeb.Gettext, "Tournament #{tournament.state}")
"""
-
#{gettext("Type: %{type}", type: type)}
-
#{gettext("State: %{state}", state: state)}
-
#{gettext("Starts At")}: #{tournament.starts_at} UTC
+
#{gettext("Type: %{type}", type: type)}
+
#{state}
+
#{gettext("Starts At")}: #{tournament.starts_at} UTC
"""
end
-
- defp generate_png(html_content) do
- ChromicPDF.capture_screenshot({:html, html_content}, capture_screenshot: %{format: "png"})
- end
end
diff --git a/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament_controller.ex b/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament_controller.ex
index b3e1554cc..eb04a7c23 100644
--- a/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament_controller.ex
+++ b/services/app/apps/codebattle/lib/codebattle_web/controllers/tournament_controller.ex
@@ -8,45 +8,11 @@ defmodule CodebattleWeb.TournamentController do
def index(conn, _params) do
current_user = conn.assigns[:current_user]
- conn
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Tournaments",
- description:
- "Create or join nice tournaments, have fun with your teammates! You can play `Frontend vs Backend` or `Ruby vs Js`",
- url: Routes.tournament_url(conn, :index)
- })
- |> live_render(CodebattleWeb.Live.Tournament.IndexView,
- session: %{
- "current_user" => current_user,
- "tournaments" => Tournament.Context.list_live_and_finished(current_user)
- }
+ live_render(conn, CodebattleWeb.Live.Tournament.IndexView,
+ session: %{"current_user" => current_user, "tournaments" => Tournament.Context.list_live_and_finished(current_user)}
)
end
- def admin(conn, params) do
- current_user = conn.assigns[:current_user]
- tournament = Tournament.Context.get!(params["id"])
-
- if Tournament.Helpers.can_access?(tournament, current_user, params) do
- conn
- |> put_view(CodebattleWeb.TournamentView)
- |> put_meta_tags(%{
- title: "Hexlet Codebattle • Join tournament",
- description: "Join tournament: #{String.slice(tournament.name, 0, 100)}",
- image: Routes.tournament_image_url(conn, :show, tournament.id),
- url: Routes.tournament_url(conn, :show, tournament.id)
- })
- |> put_gon(tournament_id: params["id"])
- |> put_gon(event_id: tournament.event_id)
- |> render("show.html")
- else
- conn
- |> put_status(:not_found)
- |> put_view(CodebattleWeb.ErrorView)
- |> render("404.html", %{msg: gettext("Tournament not found")})
- end
- end
-
def show(conn, params) do
current_user = conn.assigns[:current_user]
tournament = Tournament.Context.get!(params["id"])
@@ -55,8 +21,8 @@ defmodule CodebattleWeb.TournamentController do
conn
|> put_view(CodebattleWeb.TournamentView)
|> put_meta_tags(%{
- title: "Hexlet Codebattle • Join tournament",
- description: "Join tournament: #{String.slice(tournament.name, 0, 100)}",
+ title: tournament.name,
+ description: tournament.description,
image: Routes.tournament_image_url(conn, :show, tournament.id),
url: Routes.tournament_url(conn, :show, tournament.id)
})
diff --git a/services/app/apps/codebattle/lib/codebattle_web/html_image.ex b/services/app/apps/codebattle/lib/codebattle_web/html_image.ex
new file mode 100644
index 000000000..f525b7363
--- /dev/null
+++ b/services/app/apps/codebattle/lib/codebattle_web/html_image.ex
@@ -0,0 +1,52 @@
+defmodule CodebattleWeb.HtmlImage do
+ @moduledoc false
+ import Plug.Conn
+
+ @fake_html_to_image Application.compile_env(:codebattle, :fake_html_to_image, false)
+
+ @doc """
+ Renders an image from the given HTML content. It first checks the cache (using the provided cache_key);
+ if not found, it generates a PNG from the HTML, caches it, and sends it in the response.
+ """
+ def render_image(conn, cache_key, html_content) do
+ image =
+ case Codebattle.ImageCache.get_image(cache_key) do
+ nil ->
+ new_image = generate_png(html_content)
+ Codebattle.ImageCache.put_image(cache_key, new_image)
+ new_image
+
+ image ->
+ image
+ end
+
+ conn
+ |> put_resp_content_type("image/png")
+ |> send_resp(200, image)
+ end
+
+ @doc """
+ Generates a PNG image from the given HTML content.
+ If the fake HTML-to-image mode is enabled, it returns the HTML content instead.
+ """
+ def generate_png(html_content) do
+ if @fake_html_to_image do
+ html_content
+ else
+ {:html, html_content}
+ |> ChromicPDF.capture_screenshot(capture_screenshot: %{format: "png"})
+ |> then(fn {:ok, image} -> Base.decode64!(image) end)
+ end
+ end
+
+ @doc """
+ Returns the logo URL based on configuration.
+ """
+ def logo_url do
+ if logo = Application.get_env(:codebattle, :collab_logo) do
+ logo
+ else
+ "https://codebattle.hexlet.io/assets/images/logo.svg"
+ end
+ end
+end
diff --git a/services/app/apps/codebattle/lib/codebattle_web/templates/layout/app.html.heex b/services/app/apps/codebattle/lib/codebattle_web/templates/layout/app.html.heex
index df24c0746..9bd7e86ba 100644
--- a/services/app/apps/codebattle/lib/codebattle_web/templates/layout/app.html.heex
+++ b/services/app/apps/codebattle/lib/codebattle_web/templates/layout/app.html.heex
@@ -97,8 +97,9 @@
src="/assets/images/logo.svg"
/>
-
-
+
+ <%= Application.get_env(:codebattle, :app_title) %>
+
<%= unless @current_user.is_guest do %>
diff --git a/services/app/apps/codebattle/priv/gettext/ru/LC_MESSAGES/default.po b/services/app/apps/codebattle/priv/gettext/ru/LC_MESSAGES/default.po
index 7144f9e0b..47c8ba46c 100644
--- a/services/app/apps/codebattle/priv/gettext/ru/LC_MESSAGES/default.po
+++ b/services/app/apps/codebattle/priv/gettext/ru/LC_MESSAGES/default.po
@@ -245,6 +245,9 @@ msgstr "Задачи"
msgid "Tournaments"
msgstr "Турниры"
+msgid "Tournament"
+msgstr "Турнир"
+
#, elixir-format
msgid "Join Discord"
msgstr "Чат Discord"
@@ -786,3 +789,99 @@ msgstr "Связь с сервером потеряна, перезагрузи
msgid "Show full tournament table"
msgstr "Показать полную турнирную таблицу"
+
+msgid "Type: %{type}"
+msgstr "Тип: %{type}"
+
+msgid "swiss"
+msgstr "Швейцарская система"
+
+msgid "individual"
+msgstr "Олимпийская система"
+
+msgid "team"
+msgstr "Командный турнир"
+
+msgid "arena"
+msgstr "Арена"
+
+msgid "Tournament waiting_participants"
+msgstr "Турнир скоро начнется"
+
+msgid "Tournament canceled"
+msgstr "Турнир отменен"
+
+msgid "Tournament active"
+msgstr "Турнир активен"
+
+msgid "Tournament finished"
+msgstr "Турнир завершен"
+
+msgid "Starts At"
+msgstr "Начало"
+
+msgid "Game playing"
+msgstr "Игра продолжается"
+
+msgid "Game waiting_opponent"
+msgstr "Ожидание соперника"
+
+msgid "Game state: timeout"
+msgstr "Статус: время истекло"
+
+msgid "Game state: canceled"
+msgstr "Статус: игра отменена"
+
+msgid "Game state: game_over"
+msgstr "Статус: игра окончена"
+
+msgid "Game state: initial"
+msgstr "Статус: игра создается"
+
+msgid "Game state: builder"
+msgstr "Статус: игра готовится к запуску"
+
+msgid "Game state: waiting_opponent"
+msgstr "Статус: ожидание соперника"
+
+msgid "Game state: playing"
+msgstr "Статус: игра идет"
+
+msgid "Level: elementary"
+msgstr "Уровень: элементарный"
+
+msgid "Level: easy"
+msgstr "Уровень: легкий"
+
+msgid "Level: medium"
+msgstr "Уровень: средний"
+
+msgid "Level: hard"
+msgstr "Уровень: сложный"
+
+msgid "Timeout: %{sec} seconds"
+msgstr "Время на задачу: %{sec} секунд"
+
+msgid "undefined"
+msgstr "не определено"
+
+msgid "won"
+msgstr "победа"
+
+msgid "lost"
+msgstr "поражение"
+
+msgid "gave_up"
+msgstr "сдался"
+
+msgid "timeout"
+msgstr "время вышло"
+
+msgid "Game between"
+msgstr "Игра между"
+
+msgid "Game level"
+msgstr "Уровень сложности"
+
+msgid "Play with"
+msgstr "Играть с"
diff --git a/services/app/apps/codebattle/test/codebattle_web/controllers/image_controller_test.exs b/services/app/apps/codebattle/test/codebattle_web/controllers/image_controller_test.exs
index 72b21ddcc..bdc5b179c 100644
--- a/services/app/apps/codebattle/test/codebattle_web/controllers/image_controller_test.exs
+++ b/services/app/apps/codebattle/test/codebattle_web/controllers/image_controller_test.exs
@@ -37,15 +37,14 @@ defmodule CodebattleWeb.ImageControllerTest do
conn = get(conn, Routes.game_image_path(conn, :show, game.id))
assert conn.status == 200
- assert conn.resp_body =~ "game"
end
- test "returns 404 withot game", %{conn: conn} do
+ test "returns empty 200 without a game", %{conn: conn} do
response =
conn
|> get(Routes.game_image_path(conn, :show, 1_000_001))
- |> json_response(404)
+ |> response(200)
- assert response == %{"error" => ":not_found"}
+ assert response == ""
end
end
diff --git a/services/app/config/config.exs b/services/app/config/config.exs
index 488d45610..f440b1a5a 100644
--- a/services/app/config/config.exs
+++ b/services/app/config/config.exs
@@ -34,6 +34,7 @@ config :codebattle, CodebattleWeb.Gettext,
default_locale: "en"
config :codebattle, :api_key, "x-key"
+config :codebattle, :app_title, "Hexlet Codebattle"
config :codebattle, :fake_html_to_image, true
config :codebattle, :firebase,
diff --git a/services/app/config/releases.exs b/services/app/config/releases.exs
index d370d9f11..5831601b5 100644
--- a/services/app/config/releases.exs
+++ b/services/app/config/releases.exs
@@ -64,6 +64,7 @@ config :codebattle, CodebattleWeb.Endpoint,
server: true
config :codebattle, :api_key, System.get_env("CODEBATTLE_API_AUTH_KEY")
+config :codebattle, :app_title, System.get_env("CODEBATTLE_APP_TITLE", "Hexlet Codebattle")
config :codebattle, :firebase,
sender_id: System.get_env("FIREBASE_SENDER_ID"),
diff --git a/services/app/config/test.exs b/services/app/config/test.exs
index d9d48a16a..248c8f50a 100644
--- a/services/app/config/test.exs
+++ b/services/app/config/test.exs
@@ -15,6 +15,8 @@ asserts_executor =
_ -> Codebattle.AssertsService.Executor.Fake
end
+config :codebattle, ChromicPDF, on_demand: true
+
config :codebattle, Codebattle.Bot,
timeout: 60_000,
min_bot_step_timeout: 0