Skip to content

Commit 44071a7

Browse files
committed
Add oban
1 parent 38b0bec commit 44071a7

21 files changed

Lines changed: 505 additions & 122 deletions

apps/codebattle/lib/codebattle/application.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule Codebattle.Application do
2626
{Codebattle.Tournament.UpcomingRunner, []},
2727
{Codebattle.ImageCache, []},
2828
{Codebattle.Repo, []},
29+
{Oban, Application.fetch_env!(:codebattle, Oban)},
2930
{Registry, keys: :unique, name: Codebattle.Registry},
3031
CodebattleWeb.Telemetry,
3132
%{id: Codebattle.PubSub, start: {Phoenix.PubSub.Supervisor, :start_link, [[name: Codebattle.PubSub]]}},

apps/codebattle/lib/codebattle/event.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,10 @@ defmodule Codebattle.Event do
4343
:action_button_text,
4444
:confirmation_text,
4545
:dates,
46+
:group_tournament_id,
4647
:group_tournament_meta,
4748
:name,
49+
:save_results,
4850
:slug,
4951
:status,
5052
:tournament_id,
@@ -56,7 +58,9 @@ defmodule Codebattle.Event do
5658
field(:action_button_text, :string)
5759
field(:confirmation_text, :string)
5860
field(:dates, :string)
61+
field(:group_tournament_id, :integer)
5962
field(:name, :string)
63+
field(:save_results, :boolean, default: true)
6064
field(:slug, :string)
6165
field(:status, Ecto.Enum, values: [:pending, :passed, :active])
6266
field(:tournament_id, :integer)
@@ -145,8 +149,10 @@ defmodule Codebattle.Event do
145149
:action_button_text,
146150
:confirmation_text,
147151
:dates,
152+
:group_tournament_id,
148153
:group_tournament_meta,
149154
:name,
155+
:save_results,
150156
:slug,
151157
:status,
152158
:tournament_id,

apps/codebattle/lib/codebattle/event/user_event_stage.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule Codebattle.UserEvent.Stage do
1414
:slug,
1515
:status,
1616
:tournament_id,
17+
:group_tournament_id,
1718
:entrance_result,
1819
:place_in_total_rank,
1920
:place_in_category_rank,
@@ -31,6 +32,7 @@ defmodule Codebattle.UserEvent.Stage do
3132
field(:slug, :string)
3233
field(:status, Ecto.Enum, values: @statuses)
3334
field(:tournament_id, :integer)
35+
field(:group_tournament_id, :integer)
3436
field(:entrance_result, Ecto.Enum, values: @entrance_results)
3537
field(:place_in_total_rank, :integer)
3638
field(:place_in_category_rank, :integer)
@@ -50,6 +52,7 @@ defmodule Codebattle.UserEvent.Stage do
5052
:slug,
5153
:status,
5254
:tournament_id,
55+
:group_tournament_id,
5356
:entrance_result,
5457
:place_in_total_rank,
5558
:place_in_category_rank,
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule Codebattle.UserEvent.Stage.Context do
2+
@moduledoc false
3+
4+
import Ecto.Query
5+
6+
alias Codebattle.Repo
7+
alias Codebattle.UserEvent
8+
alias Codebattle.UserEvent.Stage
9+
alias Codebattle.Workers.SaveGroupTournamentResultsWorker
10+
alias Codebattle.Workers.SaveTournamentResultsWorker
11+
12+
@spec save_tournament_results_async(integer()) :: {:ok, Oban.Job.t()} | {:error, term()}
13+
def save_tournament_results_async(tournament_id) do
14+
%{tournament_id: tournament_id}
15+
|> SaveTournamentResultsWorker.new()
16+
|> Oban.insert()
17+
end
18+
19+
@spec save_group_tournament_results_async(integer()) :: {:ok, Oban.Job.t()} | {:error, term()}
20+
def save_group_tournament_results_async(group_tournament_id) do
21+
%{group_tournament_id: group_tournament_id}
22+
|> SaveGroupTournamentResultsWorker.new()
23+
|> Oban.insert()
24+
end
25+
26+
@spec save_tournament_results(integer(), list(map())) :: :ok
27+
def save_tournament_results(event_id, player_results) do
28+
Enum.each(player_results, fn player_result ->
29+
with %UserEvent{} = user_event <- UserEvent.get_by_user_id_and_event_id(player_result.user_id, event_id),
30+
%Stage{} = stage <- Enum.find(user_event.stages, &(&1.tournament_id == player_result.tournament_id)) do
31+
stage
32+
|> Stage.changeset(%{
33+
wins_count: player_result.wins_count,
34+
games_count: player_result.games_count,
35+
time_spent_in_seconds: player_result.time_spent_in_seconds,
36+
group_tournament_id: player_result[:group_tournament_id]
37+
})
38+
|> Repo.update()
39+
end
40+
end)
41+
42+
:ok
43+
end
44+
45+
@spec mark_stages_completed(integer(), integer()) :: :ok
46+
def mark_stages_completed(event_id, tournament_id) do
47+
now = DateTime.utc_now()
48+
49+
Stage
50+
|> join(:inner, [s], ue in UserEvent, on: s.user_event_id == ue.id)
51+
|> where([s, ue], ue.event_id == ^event_id and s.tournament_id == ^tournament_id)
52+
|> Repo.update_all(set: [status: :completed, finished_at: now])
53+
54+
:ok
55+
end
56+
57+
@spec mark_stages_completed_by_group_tournament(integer()) :: :ok
58+
def mark_stages_completed_by_group_tournament(group_tournament_id) do
59+
now = DateTime.utc_now()
60+
61+
Stage
62+
|> where([s], s.group_tournament_id == ^group_tournament_id)
63+
|> Repo.update_all(set: [status: :completed, finished_at: now])
64+
65+
:ok
66+
end
67+
end

apps/codebattle/lib/codebattle/group_tournament/context.ex

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,55 @@ defmodule Codebattle.GroupTournament.Context do
249249
|> ensure_server_started()
250250
end
251251

252+
@spec bulk_transfer_players(pos_integer(), list(map())) :: :ok
253+
def bulk_transfer_players(group_tournament_id, players) do
254+
now = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
255+
256+
player_entries =
257+
Enum.map(players, fn player ->
258+
%{
259+
group_tournament_id: group_tournament_id,
260+
user_id: player.id,
261+
lang: player.lang || "js",
262+
state: "active",
263+
inserted_at: now,
264+
updated_at: now
265+
}
266+
end)
267+
268+
user_group_tournament_entries =
269+
Enum.map(players, fn player ->
270+
%{
271+
group_tournament_id: group_tournament_id,
272+
user_id: player.id,
273+
state: "pending",
274+
repo_state: "pending",
275+
role_state: "pending",
276+
secret_state: "pending",
277+
repo_response: %{},
278+
role_response: %{},
279+
secret_response: %{},
280+
last_error: %{},
281+
inserted_at: now,
282+
updated_at: now
283+
}
284+
end)
285+
286+
player_entries
287+
|> Enum.chunk_every(1000)
288+
|> Enum.each(fn chunk ->
289+
Repo.insert_all(GroupTournamentPlayer, chunk, on_conflict: :nothing)
290+
end)
291+
292+
user_group_tournament_entries
293+
|> Enum.chunk_every(1000)
294+
|> Enum.each(fn chunk ->
295+
Repo.insert_all(UserGroupTournament, chunk, on_conflict: :nothing)
296+
end)
297+
298+
:ok
299+
end
300+
252301
defp enrich(%GroupTournament{} = group_tournament) do
253302
Map.put(group_tournament, :players_count, length(group_tournament.players || []))
254303
end

apps/codebattle/lib/codebattle/group_tournament.ex renamed to apps/codebattle/lib/codebattle/group_tournament/group_tournament.ex

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ defmodule Codebattle.GroupTournament do
55

66
import Ecto.Changeset
77

8+
alias Codebattle.Event
89
alias Codebattle.GroupTask
910
alias Codebattle.GroupTournamentPlayer
11+
alias Codebattle.Tournament
1012
alias Codebattle.User
1113

1214
@states ~w(waiting_participants active finished canceled)
@@ -17,6 +19,7 @@ defmodule Codebattle.GroupTournament do
1719
only: [
1820
:id,
1921
:creator_id,
22+
:event_id,
2023
:group_task_id,
2124
:name,
2225
:slug,
@@ -34,12 +37,15 @@ defmodule Codebattle.GroupTournament do
3437
:meta,
3538
:require_invitation,
3639
:run_on_external_platform,
37-
:template_id
40+
:template_id,
41+
:tournament_id
3842
]}
3943

4044
schema "group_tournaments" do
4145
belongs_to(:creator, User)
46+
belongs_to(:event, Event)
4247
belongs_to(:group_task, GroupTask)
48+
belongs_to(:tournament, Tournament)
4349

4450
field(:name, :string)
4551
field(:slug, :string)
@@ -72,6 +78,7 @@ defmodule Codebattle.GroupTournament do
7278
group_tournament
7379
|> cast(attrs, [
7480
:creator_id,
81+
:event_id,
7582
:group_task_id,
7683
:name,
7784
:slug,
@@ -87,6 +94,7 @@ defmodule Codebattle.GroupTournament do
8794
:require_invitation,
8895
:run_on_external_platform,
8996
:template_id,
97+
:tournament_id,
9098
:last_round_started_at,
9199
:last_round_ended_at,
92100
:meta

apps/codebattle/lib/codebattle/group_tournament/server.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ defmodule Codebattle.GroupTournament.Server do
138138
|> Repo.preload([:creator, :group_task, players: [:user]])
139139
|> Map.put(:is_live, true)
140140

141+
Codebattle.UserEvent.Stage.Context.save_group_tournament_results_async(updated.id)
142+
141143
next_state =
142144
state
143145
|> cancel_finish_timer()

apps/codebattle/lib/codebattle/tournament/strategy/base.ex

Lines changed: 3 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ defmodule Codebattle.Tournament.Base do
77
alias Codebattle.Event
88
alias Codebattle.Game
99
alias Codebattle.Tournament
10-
alias Codebattle.UserEvent
1110
alias Codebattle.UserGameReport
1211

1312
@callback build_round_pairs(Tournament.t()) :: {Tournament.t(), list(list(pos_integer()))}
@@ -1328,96 +1327,13 @@ defmodule Codebattle.Tournament.Base do
13281327
tournament
13291328
end
13301329

1331-
defp maybe_save_event_results(%{event_id: event_id} = tournament) when not is_nil(event_id) do
1332-
event = Event.get!(event_id)
1333-
event_stage = find_event_stage_for_tournament(event, tournament)
1334-
1335-
has_group_tournament =
1336-
match?(%{group_tournament_meta: meta} when is_map(meta) and map_size(meta) > 0, event_stage)
1337-
1338-
if !has_group_tournament do
1339-
tournament
1340-
|> get_players()
1341-
|> Enum.reject(& &1.is_bot)
1342-
|> Enum.each(fn player ->
1343-
UserEvent.mark_stage_as_completed(event_id, player.id, %{
1344-
id: tournament.id,
1345-
wins_count: player.wins_count,
1346-
games_count: get_players_total_games_count(tournament, player),
1347-
time_spent_in_seconds:
1348-
tournament
1349-
|> get_matches(player.matches_ids)
1350-
|> Enum.map(&(&1.duration_sec || 0))
1351-
|> Enum.sum()
1352-
})
1353-
end)
1354-
end
1355-
1356-
maybe_create_group_tournament(tournament, event, event_stage)
1330+
defp maybe_save_event_results(%{event_id: _event_id} = tournament) do
1331+
Codebattle.UserEvent.Stage.Context.save_tournament_results_async(tournament.id)
1332+
tournament
13571333
end
13581334

13591335
defp maybe_save_event_results(t), do: t
13601336

1361-
defp maybe_create_group_tournament(tournament, _event, %{group_tournament_meta: meta})
1362-
when is_map(meta) and map_size(meta) > 0 do
1363-
create_group_tournament_for_stage(tournament, meta)
1364-
end
1365-
1366-
defp maybe_create_group_tournament(tournament, _event, _stage), do: tournament
1367-
1368-
defp find_event_stage_for_tournament(event, tournament) do
1369-
# For global tournaments, the event stage has tournament_id set
1370-
stage = Enum.find(event.stages, &(&1.tournament_id == tournament.id))
1371-
1372-
if stage, do: stage, else: find_event_stage_by_slug(event, tournament)
1373-
end
1374-
1375-
defp find_event_stage_by_slug(event, tournament) do
1376-
# For single play tournaments, find via user_event_stage
1377-
case find_stage_slug_from_user_event(tournament) do
1378-
nil -> nil
1379-
slug -> Event.get_stage(event, slug)
1380-
end
1381-
end
1382-
1383-
defp find_stage_slug_from_user_event(tournament) do
1384-
import Ecto.Query, only: [where: 3, select: 3, limit: 2]
1385-
1386-
Codebattle.UserEvent.Stage
1387-
|> where([s], s.tournament_id == ^tournament.id)
1388-
|> select([s], s.slug)
1389-
|> limit(1)
1390-
|> Codebattle.Repo.one()
1391-
end
1392-
1393-
defp create_group_tournament_for_stage(tournament, meta) do
1394-
attrs = %{
1395-
creator_id: tournament.creator_id,
1396-
group_task_id: meta[:group_task_id],
1397-
name: meta[:name] || "#{tournament.name} - Review",
1398-
slug: meta[:slug] || "event-#{tournament.event_id}-t-#{tournament.id}",
1399-
description: meta[:description] || tournament.description,
1400-
starts_at: DateTime.utc_now(),
1401-
rounds_count: meta[:rounds_count] || 1,
1402-
round_timeout_seconds: meta[:round_timeout_seconds] || 3600,
1403-
run_on_external_platform: meta[:run_on_external_platform] || false,
1404-
template_id: meta[:template_id]
1405-
}
1406-
1407-
case Codebattle.GroupTournament.Context.create_group_tournament(attrs) do
1408-
{:ok, group_tournament} ->
1409-
Tournament.Context.update(tournament, %{
1410-
"group_tournament_id" => group_tournament.id
1411-
})
1412-
1413-
update_struct(tournament, %{group_tournament_id: group_tournament.id})
1414-
1415-
{:error, reason} ->
1416-
Logger.error("Failed to create group tournament: #{inspect(reason)}")
1417-
tournament
1418-
end
1419-
end
1420-
14211337
defp maybe_activate_players(%{current_round_position: 0} = t), do: t
14221338

14231339
defp maybe_activate_players(t), do: t
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule Codebattle.Workers.SaveGroupTournamentResultsWorker do
2+
@moduledoc false
3+
4+
use Oban.Worker
5+
6+
alias Codebattle.Event
7+
alias Codebattle.GroupTournament
8+
alias Codebattle.UserEvent.Stage.Context, as: StageContext
9+
10+
@impl Oban.Worker
11+
def perform(%Oban.Job{args: %{"group_tournament_id" => group_tournament_id}}) do
12+
group_tournament = GroupTournament.Context.get_group_tournament!(group_tournament_id)
13+
14+
if group_tournament.event_id do
15+
event = Event.get!(group_tournament.event_id)
16+
event_stage = Enum.find(event.stages, &(&1.group_tournament_id == group_tournament_id))
17+
18+
if event_stage && event_stage.save_results != false do
19+
StageContext.mark_stages_completed_by_group_tournament(group_tournament_id)
20+
end
21+
end
22+
23+
:ok
24+
end
25+
end

0 commit comments

Comments
 (0)