Skip to content

Commit

Permalink
Add API endpoint to export competition results (#243)
Browse files Browse the repository at this point in the history
Co-authored-by: Jonatan Kłosko <jonatanklosko@gmail.com>
  • Loading branch information
coder13 and jonatanklosko authored Dec 2, 2024
1 parent a6c0ed8 commit 3f7474a
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 3 deletions.
14 changes: 12 additions & 2 deletions lib/wca_live/competitions.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ defmodule WcaLive.Competitions do
end

@doc """
Gets a single competition.
Gets a single competition by numeric id.
"""
@spec get_competition(term()) :: %Competition{} | nil
def get_competition(id), do: Repo.get(Competition, id)
Expand All @@ -60,12 +60,22 @@ defmodule WcaLive.Competitions do
@spec get_competition_by_wca_id!(String.t()) :: %Competition{}
def get_competition_by_wca_id!(wca_id), do: Repo.get_by!(Competition, wca_id: wca_id)

@spec fetch_competition(any()) :: {:error, any()} | {:ok, %{optional(atom()) => any()}}
@doc """
Gets a single competition.
Gets a single competition
"""
@spec fetch_competition(term()) :: {:ok, %Competition{}} | {:error, Ecto.Queryable.t()}
def fetch_competition(id), do: Repo.fetch(Competition, id)

@doc """
Gets a single competition by WCA id.
"""
@spec fetch_competition_by_wca_id(String.t()) ::
{:ok, %Competition{}} | {:error, Ecto.Queryable.t()}
def fetch_competition_by_wca_id(id) do
Repo.fetch(where(Competition, wca_id: ^id))
end

@doc """
Gets a single person.
"""
Expand Down
87 changes: 86 additions & 1 deletion lib/wca_live_web/controllers/competition_controller.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule WcaLiveWeb.CompetitionController do
use WcaLiveWeb, :controller

alias WcaLive.{Competitions, Scoretaking}
alias WcaLive.{Competitions, Scoretaking, Repo}

def show_wcif(conn, params) do
case Competitions.fetch_competition(params["id"]) do
Expand All @@ -26,6 +26,22 @@ defmodule WcaLiveWeb.CompetitionController do
end
end

def show_results(conn, params) do
case fetch_competition_by_id_or_wca_id(params["id_or_wca_id"]) do
{:ok, competition} ->
results = format_results(competition)

conn
|> put_status(200)
|> json(results)

{:error, _} ->
conn
|> put_status(404)
|> json(%{error: "not found"})
end
end

def enter_attempt(conn, params) do
case params do
%{
Expand Down Expand Up @@ -174,4 +190,73 @@ defmodule WcaLiveWeb.CompetitionController do
{:error, "the provided token is not valid"}
end
end

defp fetch_competition_by_id_or_wca_id(id_or_wca_id) do
case Integer.parse(id_or_wca_id) do
{id, _} -> Competitions.fetch_competition(id)
_ -> Competitions.fetch_competition_by_wca_id(id_or_wca_id)
end
end

defp format_results(competition) do
competition
|> Repo.preload(
competition_events: [rounds: [:competition_event, results: [:person]]],
people: []
)
|> competition_to_results()
end

defp competition_to_results(competition) do
result_registrant_ids =
for competition_event <- competition.competition_events,
round <- competition_event.rounds,
result <- round.results,
do: result.person.registrant_id,
into: MapSet.new()

%{
events: Enum.map(competition.competition_events, &format_competition_event/1),
persons:
for person <- competition.people, person.registrant_id in result_registrant_ids do
format_person(person)
end
}
end

defp format_competition_event(competition_event) do
%{
"eventId" => competition_event.event_id,
"rounds" => Enum.map(competition_event.rounds, &format_round/1)
}
end

defp format_round(round) do
%{
"number" => round.number,
"results" =>
for result <- round.results, result.attempts != [] do
format_result(result)
end
}
end

defp format_result(result) do
%{
"personId" => result.person.registrant_id,
"ranking" => result.ranking,
"best" => result.best,
"average" => result.average,
"attempts" => result.attempts |> Enum.map(fn attempt -> attempt.result end)
}
end

defp format_person(person) do
%{
"id" => person.registrant_id,
"wcaId" => person.wca_id,
"name" => person.name,
"country" => person.country_iso2
}
end
end
3 changes: 3 additions & 0 deletions lib/wca_live_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ defmodule WcaLiveWeb.Router do

get "/competitions/:id/wcif", CompetitionController, :show_wcif

# Public results endpoint
get "/competitions/:id_or_wca_id/results", CompetitionController, :show_results

# Public scoretaking endpoints
post "/enter-attempt", CompetitionController, :enter_attempt
post "/enter-results", CompetitionController, :enter_results
Expand Down
45 changes: 45 additions & 0 deletions test/wca_live_web/controllers/competition_controller_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,51 @@ defmodule WcaLiveWeb.CompetitionControllerTest do
end
end

describe "show_results" do
test "returns results for a competition", %{conn: conn} do
competition = insert(:competition, wca_id: "WC2019")
competition_event = insert(:competition_event, competition: competition)
round = insert(:round, competition_event: competition_event)
person = insert(:person, competition: competition)
result = insert(:result, round: round, person: person)
_person_without_result = insert(:person, competition: competition)

conn = get(conn, "/api/competitions/#{competition.id}/results")

body = json_response(conn, 200)

assert body == %{
"events" => [
%{
"eventId" => "333",
"rounds" => [
%{
"number" => 1,
"results" => [
%{
"attempts" => [900, 900, 900, 900, 900],
"average" => 900,
"best" => 900,
"personId" => result.person.registrant_id,
"ranking" => 1
}
]
}
]
}
],
"persons" => [
%{
"country" => result.person.country_iso2,
"id" => result.person.registrant_id,
"name" => result.person.name,
"wcaId" => result.person.wca_id
}
]
}
end
end

describe "enter_attempt" do
test "returns error when no token is given", %{conn: conn} do
competition = insert(:competition)
Expand Down

0 comments on commit 3f7474a

Please sign in to comment.