From c4e31f774180d2654212ddc135b060199442bdeb Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Fri, 10 May 2024 21:33:37 +0200 Subject: [PATCH 01/34] refactor!: introduce opinionated api --- lib/uof/api.ex | 124 ++++++++++++++++ lib/uof/api/descriptions.ex | 46 +++--- lib/uof/api/descriptions/bet_stop.ex | 159 +++++++++++++++++++++ lib/uof/api/descriptions/betting_status.ex | 76 ++++++++++ lib/uof/api/descriptions/match_status.ex | 98 +++++++++++++ lib/uof/api/ecto_helpers.ex | 31 ++++ mix.exs | 3 + mix.lock | 6 + 8 files changed, 520 insertions(+), 23 deletions(-) create mode 100644 lib/uof/api.ex create mode 100644 lib/uof/api/descriptions/bet_stop.ex create mode 100644 lib/uof/api/descriptions/betting_status.ex create mode 100644 lib/uof/api/descriptions/match_status.ex create mode 100644 lib/uof/api/ecto_helpers.ex diff --git a/lib/uof/api.ex b/lib/uof/api.ex new file mode 100644 index 0000000..1dd2552 --- /dev/null +++ b/lib/uof/api.ex @@ -0,0 +1,124 @@ +defmodule UOF.API do + @moduledoc """ + Utilities used by the different APIs implemented by this client. + """ + use Tesla, only: [:get, :post], docs: false + + plug(Tesla.Middleware.BaseUrl, Application.get_env(:uof_api, :base_url)) + + plug(Tesla.Middleware.Headers, [ + {"accept", "application/xml"}, + {"content-type", "application/xml"}, + {"x-access-token", Application.get_env(:uof_api, :auth_token)} + ]) + + plug(Tesla.Middleware.XML, convention: BadgerFish) + + # https://docs.betradar.com/display/BD/UOF+-+Language+Support + @supported_languages [ + # Albanian + "sqi", + # Amharic + "am", + # Arabic + "aa", + # Azerbaijan + "aze", + # Bosnian + "bs", + # Brazilian Portuguese + "br", + # Bulgarian + "bg", + # Burmese + "my", + # Chinese (simplified) + "zh", + # Chinese (traditional) + "zht", + # Croation + "hr", + # Czech + "cs", + # Danish + "da", + # Dutch + "nl", + # English + "en", + # Estonian + "et", + # Finnish + "fi", + # French + "fr", + # Georgian + "ka", + # German + "de", + # Greek + "el", + # Hebrew + "heb", + # Hindi + "hi", + # Hungarian + "hu", + # Indonesian + "Id", + # Italian + "it", + # Japanese + "ja", + # Kazakh + "kaz", + # Khmer + "km", + # Korean + "ko", + # Latvian + "lv", + # Lithuanian + "lt", + # Macedonian + "ml", + # Macedonian Cyrillic + "mk", + # Malay + "ms", + # Norwegian + "no", + # Persian/Farsi + "fa", + # Polish + "pl", + # Portuguese + "pt", + # Romanian + "ro", + # Russian + "ru", + # Serbian + "sr", + # Serbian Latin + "srl", + # Slovak + "sk", + # Slovenian + "sl", + # Spanish + "es", + # Swahili + "sw", + # Swedish + "se", + # Thai + "th", + # Turkish + "tr", + # Ukrainian + "ukr", + # Vietnamese + "vi" + ] +end diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex index f29c2b9..693a3ba 100644 --- a/lib/uof/api/descriptions.ex +++ b/lib/uof/api/descriptions.ex @@ -11,34 +11,34 @@ defmodule UOF.API.Descriptions do HTTP.get(%UOF.API.Mappings.MarketDescriptions{}, endpoint) end - @doc """ - Describe all sport-specific match status codes used during live matches in - `odds_change` messages. - """ - def match_statuses(lang \\ "en") do - endpoint = ["descriptions", lang, "match_status.xml"] + # @doc """ + # Describe all sport-specific match status codes used during live matches in + # `odds_change` messages. + # """ + # def match_statuses(lang \\ "en") do + # endpoint = ["descriptions", lang, "match_status.xml"] - HTTP.get(%UOF.API.Mappings.MatchStatusDescriptions{}, endpoint) - end + # HTTP.get(%UOF.API.Mappings.MatchStatusDescriptions{}, endpoint) + # end - @doc """ - Describe all bet stop reasons. - """ - @spec betstop_reasons :: {:ok, UOF.API.Mappings.BetStopReasonDescription.t()} - def betstop_reasons do - endpoint = ["descriptions", "betstop_reasons.xml"] + # @doc """ + # Describe all bet stop reasons. + # """ + # @spec betstop_reasons :: {:ok, UOF.API.Mappings.BetStopReasonDescription.t()} + # def betstop_reasons do + # endpoint = ["descriptions", "betstop_reasons.xml"] - HTTP.get(%UOF.API.Mappings.BetStopReasonDescriptions{}, endpoint) - end + # HTTP.get(%UOF.API.Mappings.BetStopReasonDescriptions{}, endpoint) + # end - @doc """ - Describes all betting statuses used in `odds_change` messages. - """ - def betting_statuses do - endpoint = ["descriptions", "betting_status.xml"] + # @doc """ + # Describes all betting statuses used in `odds_change` messages. + # """ + # def betting_statuses do + # endpoint = ["descriptions", "betting_status.xml"] - HTTP.get(%UOF.API.Mappings.BettingStatusDescriptions{}, endpoint) - end + # HTTP.get(%UOF.API.Mappings.BettingStatusDescriptions{}, endpoint) + # end @doc """ Get a list of all variants and which markets they are used for. diff --git a/lib/uof/api/descriptions/bet_stop.ex b/lib/uof/api/descriptions/bet_stop.ex new file mode 100644 index 0000000..1523444 --- /dev/null +++ b/lib/uof/api/descriptions/bet_stop.ex @@ -0,0 +1,159 @@ +defmodule UOF.API.Descriptions.BetStop do + @moduledoc """ + `BetStop` messages are used to indicate that all, or a set of markets should + be instantly suspended (continue to display odds, but don't accept tickets). + + `BetStop` messages are sent very rapidly, as soon as a Betradar operator + detects an issue. At the time the `BetStop` is sent, the cause is not always + available (typically not for live matches). The cause of the `BetStop` is + provided in a subsequent `OddsChange` message. + """ + use TypedEctoSchema + + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + typed_embedded_schema do + field(:id, :integer, null: false) :: non_neg_integer() + + field(:description, Ecto.Enum, + values: [ + :UNKNOWN, + :POSSIBLE_GOAL, + :POSSIBLE_RED_CARD, + :SCOUT_LOST, + :POSSIBLE_GOAL_HOME, + :POSSIBLE_GOAL_AWAY, + :POSSIBLE_RED_CARD_HOME, + :POSSIBLE_RED_CARD_AWAY, + :POSSIBLE_PENALTY, + :POSSIBLE_PENALTY_HOME, + :POSSIBLE_PENALTY_AWAY, + :CONNECTED_TO_SUPERVISOR, + :MATCH_ENDED, + :GAMEPOINT, + :TIEBREAK, + :POSSIBLE_DIRECT_FOUL_HOME, + :POSSIBLE_DIRECT_FOUL_AWAY, + :POSSIBLE_DIRECT_FOUL, + :DANGEROUS_FREE_KICK_HOME, + :DANGEROUS_FREE_KICK_AWAY, + :DANGEROUS_GOAL_POSITION_HOME, + :DANGEROUS_GOAL_POSITION_AWAY, + :GOAL_UNDER_REVIEW, + :SCORE_UNDER_REVIEW, + :DISCONNECTION, + :POSSIBLE_CHECKOUT, + :MULTIPLE_SUSPENSIONS, + :POSSIBLE_DANGEROUS_FREE_KICK, + :POSSIBLE_DANGEROUS_GOAL_POSITION, + :POSSIBLE_TOUCHDOWN_HOME, + :POSSIBLE_TOUCHDOWN_AWAY, + :POSSIBLE_FIELDGOAL_HOME, + :POSSIBLE_FIELDGOAL_AWAY, + :POSSIBLE_SAFETY_HOME, + :POSSIBLE_SAFETY_AWAY, + :POSSIBLE_TURNOVER_HOME, + :POSSIBLE_TURNOVER_AWAY, + :VIDEO_REVIEW, + :REDZONE_HOME, + :REDZONE_AWAY, + :POSSIBLE_BOUNDARY, + :POSSIBLE_WICKET, + :POSSIBLE_CHALLENGE_HOME, + :POSSIBLE_CHALLENGE_AWAY, + :POSSIBLE_TURNOVER, + :UNKNOWN_OPERATOR, + :FREEBALL, + :DEEP_BALL, + :POSSIBLE_RUN, + :MAINTENANCE, + :BASE_HIT_DELETED, + :MATCH_DELAYED, + :MATCH_POSTPONED, + :SCOUT_DISCONNECTION_TV_SIGNAL, + :POSSIBLE_PENALTY_OFFSETTING, + :POSSIBLE_PUNT_HOME, + :POSSIBLE_PUNT_AWAY, + :POSSIBLE_FOURTH_DOWN_ATTEMPT_HOME, + :POSSIBLE_FOURTH_DOWN_ATTEMPT_AWAY, + :POSSIBLE_ONSIDE_KICK_HOME, + :POSSIBLE_ONSIDE_KICK_AWAY, + :POSSIBLE_CHALLENGE, + :POSSIBLE_CARD, + :DELAYED_PENALTY, + :SHOOTOUT_BEGINS, + :EMPTY_NET, + :POSSIBLE_TRY_HOME, + :POSSIBLE_TRY_AWAY, + :POSSIBLE_DROP_GOAL_HOME, + :POSSIBLE_DROP_GOAL_AWAY, + :POSSIBLE_CARD_HOME, + :POSSIBLE_CARD_AWAY, + :POSSIBLE_PENALTY_HOME_HOCKEY, + :POSSIBLE_PENALTY_AWAY_HOCKEY, + :DELAYED_PENALTY_HOME_HOCKEY, + :DELAYED_PENALTY_AWAY_HOCKEY, + :TWO_MAN_ADVANTAGE_HOME, + :TWO_MAN_ADVANTAGE_AWAY, + :POSSIBLE_FIELD_GOAL, + :ROLLBACK_EVENT, + :POSSIBLE_DROP_KICK_HOME, + :POSSIBLE_DROP_KICK_AWAY, + :POSSIBLE_DROP_KICK, + :POSSIBLE_VIDEO_ASSISTANT_REFEREE, + :FEED_INTERRUPTION, + :POSSIBLE_SCORE, + :POSSIBLE_SCORE_HOME, + :POSSIBLE_SCORE_AWAY, + :POSSIBLE_VIDEO_ASSISTANT_REFEREE_HOME, + :POSSIBLE_VIDEO_ASSISTANT_REFEREE_AWAY + ], + null: false + ) + end + + @doc """ + List all supported bet stop reasons. + """ + @spec all() :: list(BetStop.t()) + def all() do + case UOF.API.get("/descriptions/betstop_reasons.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("betstop_reasons_descriptions") + |> Map.get("betstop_reason") + |> Enum.map(fn x -> + {:ok, x} = cast(x) + x + end) + + {:error, _} = error -> + error + end + end + + defp cast(params) do + %__MODULE__{} + |> cast(attrs(params), [:id, :description]) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp attrs(params) do + params + |> Enum.map(fn + {"@id", val} -> {:id, val} + {"@description", val} -> {:description, val} + x -> x + end) + |> Map.new() + end +end diff --git a/lib/uof/api/descriptions/betting_status.ex b/lib/uof/api/descriptions/betting_status.ex new file mode 100644 index 0000000..351feca --- /dev/null +++ b/lib/uof/api/descriptions/betting_status.ex @@ -0,0 +1,76 @@ +defmodule UOF.API.Descriptions.BettingStatus do + @moduledoc """ + When betting markets get opened after a `BetStop` but it is still early after + the markets got closed due to a `BetStop`, `OddsChange` messages may be + annotated with a `BettingStatus` explaining the reason of a preceeding `BetStop` + message. + + Risk sensitive users may decide to keep markets closed until `BettingStatus` is + no longer present in `OddsChange` messages. + """ + use TypedEctoSchema + + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + typed_embedded_schema do + field(:id, :integer, null: false) :: non_neg_integer() + + field(:description, Ecto.Enum, + values: [ + :UNKNOWN, + :GOAL, + :DANGEROUS_FREE_KICK, + :DANGEROUS_GOAL_POSITION, + :POSSIBLE_BOUNDARY, + :POSSIBLE_CHECKOUT, + :INGAME_PENALTY + ], + null: false + ) + end + + def cast(params) do + %__MODULE__{} + |> cast(attrs(params), [:id, :description]) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp attrs(params) do + params + |> Enum.map(fn + {"@id", val} -> {:id, val} + {"@description", val} -> {:description, val} + x -> x + end) + |> Map.new() + end + + @doc """ + List all supported betting statuses. + """ + @spec all() :: list(BettingStatus.t()) + def all() do + case UOF.API.get("/descriptions/betting_status.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("betting_status_descriptions") + |> Map.get("betting_status") + |> Enum.map(fn x -> + {:ok, x} = cast(x) + x + end) + + {:error, _} = error -> + error + end + end +end diff --git a/lib/uof/api/descriptions/match_status.ex b/lib/uof/api/descriptions/match_status.ex new file mode 100644 index 0000000..48a3a3b --- /dev/null +++ b/lib/uof/api/descriptions/match_status.ex @@ -0,0 +1,98 @@ +defmodule UOF.API.Descriptions.MatchStatus do + @moduledoc """ + `OddsChange` messages for live matches may be annotated with `MatchStatus` + information. + + `MatchStatus` provides additional information about the status of a live + fixture (eg. first period, second break). + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all sport-specific match statuses. + + By default, match status descriptions are provided in English. These can + be retrieved in another language by specifying a valid language as `lang`. + """ + @spec all(lang :: String.t()) :: list(MatchStatus.t()) + def all(lang \\ "en") do + case UOF.API.get("/descriptions/#{lang}/match_status.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("match_status_descriptions") + |> Map.get("match_status") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + defmodule Sports do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + embedded_schema do + field(:all, :boolean, default: false) + field(:ids, {:array, :string}, default: []) + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, prepare(params), [:all, :ids]) + end + + defp prepare(params) do + params + |> rename_fields + |> prepare_ids + end + + defp prepare_ids(params) do + ids = + params + |> Map.get("sport", []) + |> Enum.map(fn + {"@id", id} -> id + %{"@id" => id} -> id + end) + + Map.put(params, "ids", ids) + end + end + + @primary_key false + + embedded_schema do + field(:id, :integer) + field(:description, :string) + field(:period_number, :integer) + embeds_one(:sports, Sports) + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(prepare(params), [:id, :description, :period_number]) + |> cast_embed(:sports) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp prepare(params) do + params + |> rename_fields + end +end diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex new file mode 100644 index 0000000..53fe52f --- /dev/null +++ b/lib/uof/api/ecto_helpers.ex @@ -0,0 +1,31 @@ +defmodule UOF.API.EctoHelpers do + @moduledoc """ + Ecto Helpers is a module that is ensuring we have the common functions in + use for most of the schemas or modules that uses Ecto. + """ + import Ecto.Changeset + + @doc """ + Traverse errors is a way to retrieve in a plain format the full list of + errors for all of the schemas and embedded schemas under the main one. + """ + def traverse_errors(changeset) do + traverse_errors(changeset, fn {msg, opts} -> + Regex.replace(~r"%{(\w+)}", msg, fn _, key -> + opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string() + end) + end) + end + + @doc """ + Remove leading '@' from field names in the `params` map. + """ + def rename_fields(params) do + params + |> Enum.map(fn + {<<"@", key::binary>>, value} -> {key, value} + other -> other + end) + |> Map.new() + end +end diff --git a/mix.exs b/mix.exs index 9f7fbb9..c8af975 100644 --- a/mix.exs +++ b/mix.exs @@ -27,6 +27,9 @@ defmodule UofApi.MixProject do {:saxaboom, "0.2.1"}, {:saxy, "~> 1.5"}, {:req, "~> 0.4.14"}, + {:tesla, "~> 1.9"}, + {:tesla_middleware_xml, "~> 1.0.1"}, + {:typed_ecto_schema, "~> 0.4.1"}, {:xml_builder, "~> 2.3"}, # dev {:ex_doc, "~> 0.31", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index 88bfee4..fe4b83e 100644 --- a/mix.lock +++ b/mix.lock @@ -1,6 +1,8 @@ %{ "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, + "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ex_doc": {:hex, :ex_doc, "0.32.1", "21e40f939515373bcdc9cffe65f3b3543f05015ac6c3d01d991874129d173420", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5142c9db521f106d61ff33250f779807ed2a88620e472ac95dc7d59c380113da"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, @@ -21,5 +23,9 @@ "saxy": {:hex, :saxy, "1.5.0", "0141127f2d042856f135fb2d94e0beecda7a2306f47546dbc6411fc5b07e28bf", [:mix], [], "hexpm", "ea7bb6328fbd1f2aceffa3ec6090bfb18c85aadf0f8e5030905e84235861cf89"}, "stream_split": {:hex, :stream_split, "0.1.7", "2d3fd1fd21697da7f91926768d65f79409086052c9ec7ae593987388f52425f8", [:mix], [], "hexpm", "1dc072ff507a64404a0ad7af90df97096183fee8eeac7b300320cea7c4679147"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, + "tesla_middleware_xml": {:hex, :tesla_middleware_xml, "1.0.1", "e819e510734d166b1155f72cda3f5ea006352779c3fb1206a9599bddc9e9cac0", [:make, :mix], [{:tesla, "~> 1.9", [hex: :tesla, repo: "hexpm", optional: false]}, {:xml_json, "~> 0.4.2", [hex: :xml_json, repo: "hexpm", optional: false]}], "hexpm", "c8bd2f253163c9e411ab617489b7225977eb189717fd6534026534e99b9fa5c9"}, + "typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"}, "xml_builder": {:hex, :xml_builder, "2.3.0", "69d214c6ad41ae1300b36acff4367551cdfd9dc1b860affc16e103c6b1589053", [:mix], [], "hexpm", "972ec33346a225cd5acd14ab23d4e79042bd37cb904e07e24cd06992dde1a0ed"}, + "xml_json": {:hex, :xml_json, "0.4.2", "dfb8f34e5dc82b2642ea71bc92b853098f8d110367d40e26c634244bc2ebe04c", [:mix], [{:saxy, "~> 1.2", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "9102dfa0b2878a8808262d1602185543735acd933c978fb4c4a1b065c5f0ec79"}, } From a2f828e4d3d18c4cf57bb425a090db380fce1bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sat, 11 May 2024 09:52:42 +0000 Subject: [PATCH 02/34] remove dead code --- lib/uof/api/descriptions.ex | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex index 693a3ba..97bcac6 100644 --- a/lib/uof/api/descriptions.ex +++ b/lib/uof/api/descriptions.ex @@ -11,35 +11,6 @@ defmodule UOF.API.Descriptions do HTTP.get(%UOF.API.Mappings.MarketDescriptions{}, endpoint) end - # @doc """ - # Describe all sport-specific match status codes used during live matches in - # `odds_change` messages. - # """ - # def match_statuses(lang \\ "en") do - # endpoint = ["descriptions", lang, "match_status.xml"] - - # HTTP.get(%UOF.API.Mappings.MatchStatusDescriptions{}, endpoint) - # end - - # @doc """ - # Describe all bet stop reasons. - # """ - # @spec betstop_reasons :: {:ok, UOF.API.Mappings.BetStopReasonDescription.t()} - # def betstop_reasons do - # endpoint = ["descriptions", "betstop_reasons.xml"] - - # HTTP.get(%UOF.API.Mappings.BetStopReasonDescriptions{}, endpoint) - # end - - # @doc """ - # Describes all betting statuses used in `odds_change` messages. - # """ - # def betting_statuses do - # endpoint = ["descriptions", "betting_status.xml"] - - # HTTP.get(%UOF.API.Mappings.BettingStatusDescriptions{}, endpoint) - # end - @doc """ Get a list of all variants and which markets they are used for. """ From 6adb7cadb8c07ab899a45510cc7c0f7475d8f08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sat, 11 May 2024 10:09:03 +0000 Subject: [PATCH 03/34] update tests --- test/uof/api/descriptions/bet_stop_test.exs | 23 ++++++++ .../api/descriptions/betting_status_test.exs | 23 ++++++++ .../api/descriptions/match_status_test.exs | 47 ++++++++++++++++ test/uof/api/descriptions_test.exs | 56 ------------------- 4 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 test/uof/api/descriptions/bet_stop_test.exs create mode 100644 test/uof/api/descriptions/betting_status_test.exs create mode 100644 test/uof/api/descriptions/match_status_test.exs diff --git a/test/uof/api/descriptions/bet_stop_test.exs b/test/uof/api/descriptions/bet_stop_test.exs new file mode 100644 index 0000000..2c4c3d8 --- /dev/null +++ b/test/uof/api/descriptions/bet_stop_test.exs @@ -0,0 +1,23 @@ +defmodule UOF.API.Descriptions.BetStop.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/betstop_reasons.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.BetStop.all/0 response" do + resp = UOF.API.Descriptions.BetStop.all() + + assert Enum.count(resp) == 90 + assert hd(resp).id == 0 + assert hd(resp).description == :UNKNOWN + end +end diff --git a/test/uof/api/descriptions/betting_status_test.exs b/test/uof/api/descriptions/betting_status_test.exs new file mode 100644 index 0000000..19f361c --- /dev/null +++ b/test/uof/api/descriptions/betting_status_test.exs @@ -0,0 +1,23 @@ +defmodule UOF.API.Descriptions.BettingStatus.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/betting_status.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.BettingStatus.all/0 response" do + resp = UOF.API.Descriptions.BettingStatus.all() + + assert Enum.count(resp) == 7 + assert hd(resp).id == 0 + assert hd(resp).description == :UNKNOWN + end +end diff --git a/test/uof/api/descriptions/match_status_test.exs b/test/uof/api/descriptions/match_status_test.exs new file mode 100644 index 0000000..9a8c5c2 --- /dev/null +++ b/test/uof/api/descriptions/match_status_test.exs @@ -0,0 +1,47 @@ +defmodule UOF.API.Descriptions.MatchStatus.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/match_status.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.MatchStatus/0 response" do + resp = UOF.API.Descriptions.MatchStatus.all() + + assert Enum.count(resp) == 185 + + assert Enum.at(resp, 0).id == 0 + assert Enum.at(resp, 0).description == "Not started" + assert Enum.at(resp, 0).period_number == nil + assert Enum.at(resp, 0).sports.all == true + assert Enum.at(resp, 0).sports.ids == [] + + assert Enum.at(resp, 1).id == 1 + assert Enum.at(resp, 1).description == "1st period" + assert Enum.at(resp, 1).period_number == 1 + assert Enum.at(resp, 1).sports.all == false + + assert Enum.at(resp, 1).sports.ids == + [ + "sr:sport:2", + "sr:sport:4", + "sr:sport:7", + "sr:sport:13", + "sr:sport:24", + "sr:sport:32", + "sr:sport:34", + "sr:sport:131", + "sr:sport:153", + "sr:sport:157", + "sr:sport:195" + ] + end +end diff --git a/test/uof/api/descriptions_test.exs b/test/uof/api/descriptions_test.exs index 03373ab..12d0674 100644 --- a/test/uof/api/descriptions_test.exs +++ b/test/uof/api/descriptions_test.exs @@ -27,62 +27,6 @@ defmodule UOF.API.Descriptions.Test do assert Enum.map(market.outcomes, & &1.id) == ["13", "12"] end - test "can parse UOF.API.Descriptions.match_statuses/{0, 1} response" do - {:ok, desc} = UOF.API.Descriptions.match_statuses() - - assert Enum.count(desc.statuses) == 185 - - status1 = Enum.at(desc.statuses, 0) - - assert status1.id == 0 - assert status1.description == "Not started" - assert status1.period_number == nil - assert status1.all_sports == true - assert status1.sports == [] - - status2 = Enum.at(desc.statuses, 1) - - assert status2.id == 1 - assert status2.description == "1st period" - assert status2.period_number == 1 - assert status2.all_sports == false - - assert Enum.map(status2.sports, & &1.id) == - [ - "sr:sport:2", - "sr:sport:4", - "sr:sport:7", - "sr:sport:13", - "sr:sport:24", - "sr:sport:32", - "sr:sport:34", - "sr:sport:131", - "sr:sport:153", - "sr:sport:157", - "sr:sport:195" - ] - end - - test "can parse UOF.API.Descriptions.betstop_reasons/0 response" do - {:ok, desc} = UOF.API.Descriptions.betstop_reasons() - - reason = hd(desc.reasons) - - assert Enum.count(desc.reasons) == 90 - assert reason.id == 0 - assert reason.description == "UNKNOWN" - end - - test "can parse UOF.API.Descriptions.betting_statuses/0 response" do - {:ok, desc} = UOF.API.Descriptions.betting_statuses() - - status = hd(desc.statuses) - - assert Enum.count(desc.statuses) == 7 - assert status.id == 0 - assert status.description == "UNKNOWN" - end - test "can parse UOF.API.Descriptions.void_reasons/0 response" do {:ok, desc} = UOF.API.Descriptions.void_reasons() From 23edcf76e2c24204b99d403ae53cfea9f825ad01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sat, 11 May 2024 10:44:03 +0000 Subject: [PATCH 04/34] remove dead code --- lib/uof/api/mappings/betstop_reason.ex | 14 ------------- .../mappings/betstop_reason_descriptions.ex | 14 ------------- lib/uof/api/mappings/betting_status.ex | 14 ------------- .../mappings/betting_status_descriptions.ex | 14 ------------- lib/uof/api/mappings/match_status.ex | 21 ------------------- .../api/mappings/match_status_descriptions.ex | 10 --------- 6 files changed, 87 deletions(-) delete mode 100644 lib/uof/api/mappings/betstop_reason.ex delete mode 100644 lib/uof/api/mappings/betstop_reason_descriptions.ex delete mode 100644 lib/uof/api/mappings/betting_status.ex delete mode 100644 lib/uof/api/mappings/betting_status_descriptions.ex delete mode 100644 lib/uof/api/mappings/match_status.ex delete mode 100644 lib/uof/api/mappings/match_status_descriptions.ex diff --git a/lib/uof/api/mappings/betstop_reason.ex b/lib/uof/api/mappings/betstop_reason.ex deleted file mode 100644 index 3cae8c6..0000000 --- a/lib/uof/api/mappings/betstop_reason.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule UOF.API.Mappings.BetStopReason do - @moduledoc false - use Saxaboom.Mapper - - @type t :: %__MODULE__{ - id: integer, - description: String.t() - } - - document do - attribute(:id, cast: :integer) - attribute(:description) - end -end diff --git a/lib/uof/api/mappings/betstop_reason_descriptions.ex b/lib/uof/api/mappings/betstop_reason_descriptions.ex deleted file mode 100644 index 516680d..0000000 --- a/lib/uof/api/mappings/betstop_reason_descriptions.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule UOF.API.Mappings.BetStopReasonDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.BetStopReason - - @type t :: %__MODULE__{ - reasons: list(BetStopReason.t()) - } - - document do - elements(:betstop_reason, as: :reasons, into: %BetStopReason{}) - end -end diff --git a/lib/uof/api/mappings/betting_status.ex b/lib/uof/api/mappings/betting_status.ex deleted file mode 100644 index ed52cb1..0000000 --- a/lib/uof/api/mappings/betting_status.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule UOF.API.Mappings.BettingStatus do - @moduledoc false - use Saxaboom.Mapper - - @type t :: %__MODULE__{ - id: integer, - description: String.t() - } - - document do - attribute(:id, cast: :integer) - attribute(:description) - end -end diff --git a/lib/uof/api/mappings/betting_status_descriptions.ex b/lib/uof/api/mappings/betting_status_descriptions.ex deleted file mode 100644 index e061fc0..0000000 --- a/lib/uof/api/mappings/betting_status_descriptions.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule UOF.API.Mappings.BettingStatusDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.BettingStatus - - @type t :: %__MODULE__{ - statuses: list(BettingStatus.t()) - } - - document do - elements(:betting_status, as: :statuses, into: %BettingStatus{}) - end -end diff --git a/lib/uof/api/mappings/match_status.ex b/lib/uof/api/mappings/match_status.ex deleted file mode 100644 index 7bdc855..0000000 --- a/lib/uof/api/mappings/match_status.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule UOF.API.Mappings.MatchStatus.Sport do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id) - end -end - -defmodule UOF.API.Mappings.MatchStatus do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id, cast: :integer) - attribute(:description) - attribute(:period_number, cast: :integer) - attribute(:all, as: :all_sports, cast: :boolean, default: false) - elements(:sport, as: :sports, into: %UOF.API.Mappings.MatchStatus.Sport{}) - end -end diff --git a/lib/uof/api/mappings/match_status_descriptions.ex b/lib/uof/api/mappings/match_status_descriptions.ex deleted file mode 100644 index d9cd5b3..0000000 --- a/lib/uof/api/mappings/match_status_descriptions.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.MatchStatusDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.MatchStatus - - document do - elements(:match_status, as: :statuses, into: %MatchStatus{}) - end -end From 082d200569ec3440fbd188d2de65b497f87f304e Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 13:22:21 +0200 Subject: [PATCH 05/34] void reasons --- lib/uof/api/descriptions.ex | 9 --- lib/uof/api/descriptions/void_reason.ex | 81 +++++++++++++++++++ .../uof/api/descriptions/void_reason_test.exs | 23 ++++++ test/uof/api/descriptions_test.exs | 10 --- 4 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 lib/uof/api/descriptions/void_reason.ex create mode 100644 test/uof/api/descriptions/void_reason_test.exs diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex index 97bcac6..4e51d61 100644 --- a/lib/uof/api/descriptions.ex +++ b/lib/uof/api/descriptions.ex @@ -28,13 +28,4 @@ defmodule UOF.API.Descriptions do HTTP.get(%UOF.API.Mappings.Producers{}, endpoint) end - - @doc """ - Describe all possible void reasons used in `bet_settlement` messages. - """ - def void_reasons do - endpoint = ["descriptions", "void_reasons.xml"] - - HTTP.get(%UOF.API.Mappings.VoidReasonDescriptions{}, endpoint) - end end diff --git a/lib/uof/api/descriptions/void_reason.ex b/lib/uof/api/descriptions/void_reason.ex new file mode 100644 index 0000000..b90ccdc --- /dev/null +++ b/lib/uof/api/descriptions/void_reason.ex @@ -0,0 +1,81 @@ +# https://docs.betradar.com/display/BD/UOF+API+-+Void+Reasons +defmodule UOF.API.Descriptions.VoidReason do + @moduledoc """ + `BetSettlement` and `BetCancel` messages may be annotated with a `VoidReason` + for a particular market. This happens when at least on of the aoutcomes. + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all sport-specific match statuses. + + By default, match status descriptions are provided in English. These can + be retrieved in another language by specifying a valid language as `lang`. + """ + @spec all() :: list(VoidReason.t()) + def all() do + case UOF.API.get("/descriptions/void_reasons.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("void_reasons_descriptions") + |> Map.get("void_reason") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + @primary_key false + + embedded_schema do + field(:id, :integer) + + field(:description, Ecto.Enum, + values: [ + :OTHER, + # deprecated as per Betradar's official documentation + :NO_GOALSCORER, + # deprecated as per Betradar's official documentation + :CORRECT_SCORE_MISSING, + :RESULT_UNVERIFIABLE, + :FORMAT_CHANGE, + :CANCELLED_EVENT, + # deprecated as per Betradar's official documentation + :MISSING_GOALSCORER, + :MATCH_ENDED_IN_WALKOVER, + :DEAD_HEAT, + :RETIRED_OR_DEFAULTED, + :EVENT_ABANDONED, + :EVENT_POSTPONED, + :INCORRECT_ODDS, + :INCORRECT_STATISTICS, + :NO_RESULT_ASSIGNABLE, + :CLIENT_SIDE_SETTLEMENT_NEEDED, + :STARTING_PITCHER_CHANGED + ] + ) + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(prepare(params), [:id, :description]) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp prepare(params) do + params + |> rename_fields + end +end diff --git a/test/uof/api/descriptions/void_reason_test.exs b/test/uof/api/descriptions/void_reason_test.exs new file mode 100644 index 0000000..7ed5a2b --- /dev/null +++ b/test/uof/api/descriptions/void_reason_test.exs @@ -0,0 +1,23 @@ +defmodule UOF.API.Descriptions.VoidReason.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/void_reasons.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.VoidReason.all/0 response" do + resp = UOF.API.Descriptions.VoidReason.all() + + assert Enum.count(resp) == 17 + assert hd(resp).id == 0 + assert hd(resp).description == :OTHER + end +end diff --git a/test/uof/api/descriptions_test.exs b/test/uof/api/descriptions_test.exs index 12d0674..60d860c 100644 --- a/test/uof/api/descriptions_test.exs +++ b/test/uof/api/descriptions_test.exs @@ -27,16 +27,6 @@ defmodule UOF.API.Descriptions.Test do assert Enum.map(market.outcomes, & &1.id) == ["13", "12"] end - test "can parse UOF.API.Descriptions.void_reasons/0 response" do - {:ok, desc} = UOF.API.Descriptions.void_reasons() - - reason = hd(desc.reasons) - - assert Enum.count(desc.reasons) == 17 - assert reason.id == 0 - assert reason.description == "OTHER" - end - test "can parse UOF.API.Descriptions.variants/{0, 1} response" do {:ok, desc} = UOF.API.Descriptions.variants() From 14e679497a0f7a8b145293f51fa9151e762b66cd Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 13:47:05 +0200 Subject: [PATCH 06/34] producers --- lib/uof/api/descriptions.ex | 9 --- lib/uof/api/descriptions/producers.ex | 73 +++++++++++++++++++ lib/uof/api/mappings/producer.ex | 14 ---- lib/uof/api/mappings/producers.ex | 10 --- lib/uof/api/mappings/void_reason.ex | 9 --- .../api/mappings/void_reason_descriptions.ex | 10 --- test/uof/api/descriptions/producer_test.exs | 28 +++++++ test/uof/api/descriptions_test.exs | 15 ---- 8 files changed, 101 insertions(+), 67 deletions(-) create mode 100644 lib/uof/api/descriptions/producers.ex delete mode 100644 lib/uof/api/mappings/producer.ex delete mode 100644 lib/uof/api/mappings/producers.ex delete mode 100644 lib/uof/api/mappings/void_reason.ex delete mode 100644 lib/uof/api/mappings/void_reason_descriptions.ex create mode 100644 test/uof/api/descriptions/producer_test.exs diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex index 4e51d61..72f0cb0 100644 --- a/lib/uof/api/descriptions.ex +++ b/lib/uof/api/descriptions.ex @@ -19,13 +19,4 @@ defmodule UOF.API.Descriptions do HTTP.get(%UOF.API.Mappings.VariantDescriptions{}, endpoint) end - - @doc """ - Describe all currently avbailable producers and their ids. - """ - def producers do - endpoint = ["descriptions", "producers.xml"] - - HTTP.get(%UOF.API.Mappings.Producers{}, endpoint) - end end diff --git a/lib/uof/api/descriptions/producers.ex b/lib/uof/api/descriptions/producers.ex new file mode 100644 index 0000000..5442447 --- /dev/null +++ b/lib/uof/api/descriptions/producers.ex @@ -0,0 +1,73 @@ +# https://docs.betradar.com/display/BD/UOF+-+Producers +defmodule UOF.API.Descriptions.Producer do + @moduledoc """ + Unified Odds Feed handles data from multiple sources, which are called + odds or message producers. + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all available producers. + """ + @spec all() :: list(VoidReason.t()) + def all() do + case UOF.API.get("/descriptions/producers.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("producers") + |> Map.get("producer") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + @primary_key false + + embedded_schema do + field(:id, :integer) + field(:name, :string) + field(:description, :string) + field(:api_url, :string) + field(:active, :boolean) + field(:scope, {:array, Ecto.Enum}, values: [:live, :prematch, :virtual]) + field(:stateful_recovery_window_in_minutes, :integer) + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(prepare(params), [ + :id, + :name, + :description, + :api_url, + :active, + :scope, + :stateful_recovery_window_in_minutes + ]) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp prepare(params) do + params + |> rename_fields + |> prepare_scope + end + + defp prepare_scope(params) do + scope = String.split(params["scope"], "|") + Map.put(params, "scope", scope) + end +end diff --git a/lib/uof/api/mappings/producer.ex b/lib/uof/api/mappings/producer.ex deleted file mode 100644 index bbb9ee1..0000000 --- a/lib/uof/api/mappings/producer.ex +++ /dev/null @@ -1,14 +0,0 @@ -defmodule UOF.API.Mappings.Producer do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id, cast: :integer) - attribute(:name) - attribute(:description) - attribute(:api_url) - attribute(:active, cast: :boolean) - attribute(:scope) - attribute(:stateful_recovery_window_in_minutes, cast: :integer) - end -end diff --git a/lib/uof/api/mappings/producers.ex b/lib/uof/api/mappings/producers.ex deleted file mode 100644 index 3362ba5..0000000 --- a/lib/uof/api/mappings/producers.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.Producers do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Producer - - document do - elements(:producer, as: :producers, into: %Producer{}) - end -end diff --git a/lib/uof/api/mappings/void_reason.ex b/lib/uof/api/mappings/void_reason.ex deleted file mode 100644 index fcaefe7..0000000 --- a/lib/uof/api/mappings/void_reason.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule UOF.API.Mappings.VoidReason do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id, cast: :integer) - attribute(:description) - end -end diff --git a/lib/uof/api/mappings/void_reason_descriptions.ex b/lib/uof/api/mappings/void_reason_descriptions.ex deleted file mode 100644 index 2b5982a..0000000 --- a/lib/uof/api/mappings/void_reason_descriptions.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.VoidReasonDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.VoidReason - - document do - elements(:void_reason, as: :reasons, into: %VoidReason{}) - end -end diff --git a/test/uof/api/descriptions/producer_test.exs b/test/uof/api/descriptions/producer_test.exs new file mode 100644 index 0000000..5bc8cd4 --- /dev/null +++ b/test/uof/api/descriptions/producer_test.exs @@ -0,0 +1,28 @@ +defmodule UOF.API.Descriptions.Producer.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/producers.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.Producer.all/0 response" do + resp = UOF.API.Descriptions.Producer.all() + + assert Enum.count(resp) == 15 + assert hd(resp).id == 1 + assert hd(resp).name == "LO" + assert hd(resp).description == "Live Odds" + assert hd(resp).api_url == "https://stgapi.betradar.com/v1/liveodds/" + assert hd(resp).active == true + assert hd(resp).scope == [:live] + assert hd(resp).stateful_recovery_window_in_minutes == 600 + end +end diff --git a/test/uof/api/descriptions_test.exs b/test/uof/api/descriptions_test.exs index 60d860c..4402643 100644 --- a/test/uof/api/descriptions_test.exs +++ b/test/uof/api/descriptions_test.exs @@ -50,19 +50,4 @@ defmodule UOF.API.Descriptions.Test do assert outcome_mapping.product_outcome_id == "26" assert outcome_mapping.product_outcome_name == "4:0" end - - test "can parse UOF.API.Descriptions.producers/0 response" do - {:ok, desc} = UOF.API.Descriptions.producers() - - producer = hd(desc.producers) - - assert Enum.count(desc.producers) == 15 - assert producer.id == 1 - assert producer.name == "LO" - assert producer.description == "Live Odds" - assert producer.api_url == "https://stgapi.betradar.com/v1/liveodds/" - assert producer.active == true - assert producer.scope == "live" - assert producer.stateful_recovery_window_in_minutes == 600 - end end From 45e057675c20070b40f47d0e6ffeff42cf51b231 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 15:52:58 +0200 Subject: [PATCH 07/34] markets --- .gitignore | 4 +- lib/uof/api/descriptions.ex | 10 -- lib/uof/api/descriptions/market.ex | 149 ++++++++++++++++++++ lib/uof/api/mappings/market.ex | 15 -- lib/uof/api/mappings/market_descriptions.ex | 10 -- test/uof/api/descriptions/market_test.exs | 25 ++++ test/uof/api/descriptions_test.exs | 12 -- 7 files changed, 177 insertions(+), 48 deletions(-) create mode 100644 lib/uof/api/descriptions/market.ex delete mode 100644 lib/uof/api/mappings/market.ex delete mode 100644 lib/uof/api/mappings/market_descriptions.ex create mode 100644 test/uof/api/descriptions/market_test.exs diff --git a/.gitignore b/.gitignore index faa7a0e..ddd5b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,6 @@ erl_crash.dump hex.config *.ez uof_messages-*.tar -*~ \ No newline at end of file +*~ +\#* +.\#* \ No newline at end of file diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex index 72f0cb0..a1c91b2 100644 --- a/lib/uof/api/descriptions.ex +++ b/lib/uof/api/descriptions.ex @@ -1,16 +1,6 @@ defmodule UOF.API.Descriptions do alias UOF.API.Utils.HTTP - @doc """ - Describe all currently available markets. - """ - def markets(lang \\ "en") do - # TO-DO: Optional mappings - endpoint = ["descriptions", lang, "markets.xml"] - - HTTP.get(%UOF.API.Mappings.MarketDescriptions{}, endpoint) - end - @doc """ Get a list of all variants and which markets they are used for. """ diff --git a/lib/uof/api/descriptions/market.ex b/lib/uof/api/descriptions/market.ex new file mode 100644 index 0000000..a0e2271 --- /dev/null +++ b/lib/uof/api/descriptions/market.ex @@ -0,0 +1,149 @@ +defmodule UOF.API.Descriptions.Market do + @moduledoc """ + Markets are the betting proposition with outcomes on which punters place their + bets. Different types of markets are defined for different fixtures. + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all supported markets. + """ + @spec all(include_mappings? :: boolean(), lang :: String.t()) :: list(Market.t()) + def all(include_mappings? \\ false, lang \\ "en") do + case UOF.API.get("/descriptions/#{lang}/markets.xml", + query: [include_mappings: include_mappings?] + ) do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("market_descriptions") + |> Map.get("market") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + defmodule Outcome do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + embedded_schema do + field(:id, :integer) + field(:name, :string) + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, prepare(params), [:id, :name]) + end + + defp prepare(params) do + params + |> rename_fields + end + end + + defmodule Specifier do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + embedded_schema do + # ["decimal", "integer", "string", "variable_text"] + field(:type, :string) + field(:name, :string) + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, prepare(params), [:name, :type]) + end + + defp prepare(params) do + params + |> rename_fields + end + end + + @primary_key false + + embedded_schema do + field(:id, :integer) + field(:name, :string) + field(:groups, {:array, :string}) + # ["player", "competitor", "free_text"] + field(:outcome_type, :string) + field(:includes_outcomes_of_type, :string) + embeds_many(:outcomes, Outcome) + embeds_many(:specifiers, Specifier) + # TO-DO: embeds_many(:mappings, Mapping) + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(prepare(params), [:id, :name, :groups, :outcome_type, :includes_outcomes_of_type]) + |> cast_embed(:outcomes) + |> cast_embed(:specifiers) + |> case do + %Ecto.Changeset{valid?: true} = changeset -> + {:ok, apply_changes(changeset)} + + %Ecto.Changeset{} = changeset -> + {:error, {params, traverse_errors(changeset)}} + end + end + + defp prepare(params) do + params + |> rename_fields + |> prepare_outcomes + |> prepare_specifiers + |> split_groups + end + + defp prepare_outcomes(params) do + outcomes = + params + |> Map.get("outcomes", %{}) + |> Map.get("outcome", []) + + case outcomes do + outcome when not is_list(outcome) -> + Map.put(params, "outcomes", [outcome]) + + _ -> + Map.put(params, "outcomes", outcomes) + end + end + + defp prepare_specifiers(params) do + specifiers = + params + |> Map.get("specifiers", %{}) + |> Map.get("specifier", []) + + case specifiers do + specifier when not is_list(specifier) -> + Map.put(params, "specifiers", [specifier]) + + _ -> + Map.put(params, "specifiers", specifiers) + end + end + + defp split_groups(params) do + scope = String.split(params["groups"], "|") + Map.put(params, "groups", scope) + end +end diff --git a/lib/uof/api/mappings/market.ex b/lib/uof/api/mappings/market.ex deleted file mode 100644 index 1c8c809..0000000 --- a/lib/uof/api/mappings/market.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule UOF.API.Mappings.Market do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.{Outcome, Specifier} - - document do - attribute(:id, cast: :integer) - attribute(:name) - attribute(:groups) - # attribute :outcome_type, as: :market.outcome_type - elements(:outcome, as: :outcomes, into: %Outcome{}) - elements(:specifier, as: :specifiers, into: %Specifier{}) - end -end diff --git a/lib/uof/api/mappings/market_descriptions.ex b/lib/uof/api/mappings/market_descriptions.ex deleted file mode 100644 index 8587002..0000000 --- a/lib/uof/api/mappings/market_descriptions.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.MarketDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Market - - document do - elements(:market, as: :markets, into: %Market{}) - end -end diff --git a/test/uof/api/descriptions/market_test.exs b/test/uof/api/descriptions/market_test.exs new file mode 100644 index 0000000..7370254 --- /dev/null +++ b/test/uof/api/descriptions/market_test.exs @@ -0,0 +1,25 @@ +defmodule UOF.API.Descriptions.Market.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/markets.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Descriptions.Market.all/0 response" do + resp = UOF.API.Descriptions.Market.all() + + assert Enum.count(resp) == 1172 + assert hd(resp).id == 282 + assert hd(resp).name == "Innings 1 to 5th top - {$competitor1} total" + assert hd(resp).groups == ["all", "score", "4.5_innings"] + assert Enum.map(hd(resp).outcomes, & &1.id) == [13, 12] + end +end diff --git a/test/uof/api/descriptions_test.exs b/test/uof/api/descriptions_test.exs index 4402643..4ce888b 100644 --- a/test/uof/api/descriptions_test.exs +++ b/test/uof/api/descriptions_test.exs @@ -15,18 +15,6 @@ defmodule UOF.API.Descriptions.Test do :ok end - test "can parse UOF.API.Descriptions.markets/{0, 1} response" do - {:ok, desc} = UOF.API.Descriptions.markets() - - market = hd(desc.markets) - - assert Enum.count(desc.markets) == 1172 - assert market.id == 282 - assert market.name == "Innings 1 to 5th top - {$competitor1} total" - assert market.groups == "all|score|4.5_innings" - assert Enum.map(market.outcomes, & &1.id) == ["13", "12"] - end - test "can parse UOF.API.Descriptions.variants/{0, 1} response" do {:ok, desc} = UOF.API.Descriptions.variants() From 7521fe01bd02dd842498e76a606d52a75e65ff9b Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:17:37 +0200 Subject: [PATCH 08/34] variant --- lib/uof/api/descriptions/market.ex | 22 +++ lib/uof/api/descriptions/variant.ex | 138 ++++++++++++++++++ lib/uof/api/ecto_helpers.ex | 19 +++ .../variant_test.exs} | 34 ++--- 4 files changed, 195 insertions(+), 18 deletions(-) create mode 100644 lib/uof/api/descriptions/variant.ex rename test/uof/api/{descriptions_test.exs => descriptions/variant_test.exs} (53%) diff --git a/lib/uof/api/descriptions/market.ex b/lib/uof/api/descriptions/market.ex index a0e2271..4637739 100644 --- a/lib/uof/api/descriptions/market.ex +++ b/lib/uof/api/descriptions/market.ex @@ -29,6 +29,28 @@ defmodule UOF.API.Descriptions.Market do end end + @doc """ + """ + # UOF.API.Descriptions.Market.variant(241, "sr:exact_games:bestof:7") + def variant(market, variant, include_mappings? \\ false, lang \\ "en") do + case UOF.API.get("/descriptions/#{lang}/markets/#{market}/variants/#{variant}", + query: [include_mappings: include_mappings?] + ) do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("market_descriptions") + |> Map.get("market") + + # |> Enum.map(fn x -> + # {:ok, x} = changeset(x) + # x + # end) + + {:error, _} = error -> + error + end + end + defmodule Outcome do @moduledoc false use Ecto.Schema diff --git a/lib/uof/api/descriptions/variant.ex b/lib/uof/api/descriptions/variant.ex new file mode 100644 index 0000000..afe6d3f --- /dev/null +++ b/lib/uof/api/descriptions/variant.ex @@ -0,0 +1,138 @@ +defmodule UOF.API.Descriptions.Variant do + @moduledoc """ + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all variants. + """ + @spec all(lang :: String.t()) :: list(Variant.t()) + def all(lang \\ "en") do + case UOF.API.get("/descriptions/#{lang}/variants.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("variant_descriptions") + |> Map.get("variant") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + @primary_key false + + embedded_schema do + field(:id, :string) + + embeds_many :outcomes, Outcome, primary_key: false do + field(:id, :string) + field(:name, :string) + end + + embeds_many :mappings, Mapping, primary_key: false do + field(:product_id, :integer) + field(:product_ids, {:array, :integer}) + field(:sport_id, :string) + field(:market_id, :integer) + field(:product_market_id, :string) + + embeds_many :outcome_mappings, OutcomeMapping, primary_key: false do + field(:outcome_id, :string) + field(:product_outcome_id, :integer) + field(:product_outcome_name, :string) + end + end + end + + def changeset(model \\ %__MODULE__{}, params) do + params = prepare(params) + + model + |> cast(params, [:id]) + |> cast_embed(:outcomes, with: &outcome_changeset/2) + |> cast_embed(:mappings, with: &mapping_changeset/2) + |> apply + end + + defp prepare(params) do + params + |> rename_fields + |> prepare_outcomes + |> prepare_mappings + end + + defp prepare_outcomes(params) do + outcomes = + params + |> Map.get("outcomes", %{}) + |> Map.get("outcome", []) + + case outcomes do + outcome when not is_list(outcome) -> + Map.put(params, "outcomes", [outcome]) + + _ -> + Map.put(params, "outcomes", outcomes) + end + end + + defp prepare_mappings(params) do + mappings = + params + |> Map.get("mappings", %{}) + |> Map.get("mapping", []) + + case mappings do + mapping when not is_list(mapping) -> + Map.put(params, "mappings", [mapping]) + + _ -> + Map.put(params, "mappings", mappings) + end + end + + def outcome_changeset(model, params) do + params = prepare_outcome(params) + + model + |> cast(params, [:id, :name]) + end + + def prepare_outcome(params) do + params + |> rename_fields + end + + def mapping_changeset(model, params) do + params = prepare_mapping(params) + + model + |> cast(params, [:product_id, :product_ids, :sport_id, :market_id, :product_market_id]) + |> cast_embed(:outcome_mappings, with: &outcome_mapping_changeset/2) + end + + def prepare_mapping(params) do + params + |> rename_fields + |> split("product_ids", "|") + |> rename("mapping_outcome", "outcome_mappings", []) + end + + def outcome_mapping_changeset(model, params) do + params = prepare_outcome_mapping(params) + + model + |> cast(params, [:outcome_id, :product_outcome_id, :product_outcome_name]) + end + + def prepare_outcome_mapping(params) do + params + |> rename_fields + end +end diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex index 53fe52f..4859204 100644 --- a/lib/uof/api/ecto_helpers.ex +++ b/lib/uof/api/ecto_helpers.ex @@ -28,4 +28,23 @@ defmodule UOF.API.EctoHelpers do end) |> Map.new() end + + def split(params, field, separator) do + values = Map.get(params, field) + values = String.split(values, separator) + Map.put(params, field, values) + end + + def rename(params, old, new, default) do + {values, params} = Map.pop(params, old, default) + Map.put(params, new, values) + end + + def apply(%Ecto.Changeset{valid?: true} = changeset) do + {:ok, apply_changes(changeset)} + end + + def apply(%Ecto.Changeset{} = changeset) do + {:error, traverse_errors(changeset)} + end end diff --git a/test/uof/api/descriptions_test.exs b/test/uof/api/descriptions/variant_test.exs similarity index 53% rename from test/uof/api/descriptions_test.exs rename to test/uof/api/descriptions/variant_test.exs index 4ce888b..97b69db 100644 --- a/test/uof/api/descriptions_test.exs +++ b/test/uof/api/descriptions/variant_test.exs @@ -1,25 +1,23 @@ -defmodule UOF.API.Descriptions.Test do +defmodule UOF.API.Descriptions.Variant.Test do use ExUnit.Case - import Mock - # mock http requests towards Betradar and use recorded responses instead - setup_with_mocks([ - {UOF.API.Utils.HTTP, [:passthrough], - [ - get: fn schema, endpoint -> - data = File.read!("test/data/" <> Enum.at(endpoint, -1)) - Saxaboom.parse(data, schema) - end - ]} - ]) do + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/variants.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + :ok end - test "can parse UOF.API.Descriptions.variants/{0, 1} response" do - {:ok, desc} = UOF.API.Descriptions.variants() + test "can parse UOF.API.Descriptions.Variant.all/0 response" do + resp = UOF.API.Descriptions.Variant.all() - assert Enum.count(desc.variants) == 144 - variant = hd(desc.variants) + assert Enum.count(resp) == 144 + variant = hd(resp) assert variant.id == "sr:correct_score:after:4" assert Enum.count(variant.outcomes) == 5 outcome = hd(variant.outcomes) @@ -28,14 +26,14 @@ defmodule UOF.API.Descriptions.Test do assert Enum.count(variant.mappings) == 1 mapping = hd(variant.mappings) assert mapping.product_id == 3 - assert mapping.product_ids == "3" + assert mapping.product_ids == [3] assert mapping.sport_id == "sr:sport:22" assert mapping.market_id == 1262 assert mapping.product_market_id == "960" assert Enum.count(mapping.outcome_mappings) == 5 outcome_mapping = hd(mapping.outcome_mappings) assert outcome_mapping.outcome_id == "sr:correct_score:after:4:1530" - assert outcome_mapping.product_outcome_id == "26" + assert outcome_mapping.product_outcome_id == 26 assert outcome_mapping.product_outcome_name == "4:0" end end From ae51cfef34a35879d72032790a9314ce2bc89504 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:38:09 +0200 Subject: [PATCH 09/34] import ecto into formatter --- .formatter.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/.formatter.exs b/.formatter.exs index c185fe1..73215ba 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,5 @@ # Used by "mix format" [ + import_deps: [:ecto], inputs: ["{mix,.formatter}.exs", "{config,lib,test,examples}/**/*.{ex,exs}"] ] From 0e34d893484532b22b8e1dc316cc5f5f0c0c1df9 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:38:27 +0200 Subject: [PATCH 10/34] remove dead code --- lib/uof/api/descriptions.ex | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 lib/uof/api/descriptions.ex diff --git a/lib/uof/api/descriptions.ex b/lib/uof/api/descriptions.ex deleted file mode 100644 index a1c91b2..0000000 --- a/lib/uof/api/descriptions.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule UOF.API.Descriptions do - alias UOF.API.Utils.HTTP - - @doc """ - Get a list of all variants and which markets they are used for. - """ - def variants(lang \\ "en") do - endpoint = ["descriptions", lang, "variants.xml"] - - HTTP.get(%UOF.API.Mappings.VariantDescriptions{}, endpoint) - end -end From b12c416ee29ade1fd7459575304855659e0595b5 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:42:35 +0200 Subject: [PATCH 11/34] remove typed_ecto_schema --- lib/uof/api/descriptions/bet_stop.ex | 9 ++++----- lib/uof/api/descriptions/betting_status.ex | 9 ++++----- mix.exs | 2 +- mix.lock | 1 - 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/uof/api/descriptions/bet_stop.ex b/lib/uof/api/descriptions/bet_stop.ex index 1523444..a050605 100644 --- a/lib/uof/api/descriptions/bet_stop.ex +++ b/lib/uof/api/descriptions/bet_stop.ex @@ -8,15 +8,15 @@ defmodule UOF.API.Descriptions.BetStop do available (typically not for live matches). The cause of the `BetStop` is provided in a subsequent `OddsChange` message. """ - use TypedEctoSchema + use Ecto.Schema import Ecto.Changeset import UOF.API.EctoHelpers @primary_key false - typed_embedded_schema do - field(:id, :integer, null: false) :: non_neg_integer() + embedded_schema do + field(:id, :integer) field(:description, Ecto.Enum, values: [ @@ -110,8 +110,7 @@ defmodule UOF.API.Descriptions.BetStop do :POSSIBLE_SCORE_AWAY, :POSSIBLE_VIDEO_ASSISTANT_REFEREE_HOME, :POSSIBLE_VIDEO_ASSISTANT_REFEREE_AWAY - ], - null: false + ] ) end diff --git a/lib/uof/api/descriptions/betting_status.ex b/lib/uof/api/descriptions/betting_status.ex index 351feca..282de5f 100644 --- a/lib/uof/api/descriptions/betting_status.ex +++ b/lib/uof/api/descriptions/betting_status.ex @@ -8,15 +8,15 @@ defmodule UOF.API.Descriptions.BettingStatus do Risk sensitive users may decide to keep markets closed until `BettingStatus` is no longer present in `OddsChange` messages. """ - use TypedEctoSchema + use Ecto.Schema import Ecto.Changeset import UOF.API.EctoHelpers @primary_key false - typed_embedded_schema do - field(:id, :integer, null: false) :: non_neg_integer() + embedded_schema do + field(:id, :integer) field(:description, Ecto.Enum, values: [ @@ -27,8 +27,7 @@ defmodule UOF.API.Descriptions.BettingStatus do :POSSIBLE_BOUNDARY, :POSSIBLE_CHECKOUT, :INGAME_PENALTY - ], - null: false + ] ) end diff --git a/mix.exs b/mix.exs index c8af975..6a6b0f0 100644 --- a/mix.exs +++ b/mix.exs @@ -24,12 +24,12 @@ defmodule UofApi.MixProject do defp deps do [ + {:ecto, "~> 3.11"}, {:saxaboom, "0.2.1"}, {:saxy, "~> 1.5"}, {:req, "~> 0.4.14"}, {:tesla, "~> 1.9"}, {:tesla_middleware_xml, "~> 1.0.1"}, - {:typed_ecto_schema, "~> 0.4.1"}, {:xml_builder, "~> 2.3"}, # dev {:ex_doc, "~> 0.31", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index fe4b83e..71243c3 100644 --- a/mix.lock +++ b/mix.lock @@ -25,7 +25,6 @@ "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, "tesla_middleware_xml": {:hex, :tesla_middleware_xml, "1.0.1", "e819e510734d166b1155f72cda3f5ea006352779c3fb1206a9599bddc9e9cac0", [:make, :mix], [{:tesla, "~> 1.9", [hex: :tesla, repo: "hexpm", optional: false]}, {:xml_json, "~> 0.4.2", [hex: :xml_json, repo: "hexpm", optional: false]}], "hexpm", "c8bd2f253163c9e411ab617489b7225977eb189717fd6534026534e99b9fa5c9"}, - "typed_ecto_schema": {:hex, :typed_ecto_schema, "0.4.1", "a373ca6f693f4de84cde474a67467a9cb9051a8a7f3f615f1e23dc74b75237fa", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm", "85c6962f79d35bf543dd5659c6adc340fd2480cacc6f25d2cc2933ea6e8fcb3b"}, "xml_builder": {:hex, :xml_builder, "2.3.0", "69d214c6ad41ae1300b36acff4367551cdfd9dc1b860affc16e103c6b1589053", [:mix], [], "hexpm", "972ec33346a225cd5acd14ab23d4e79042bd37cb904e07e24cd06992dde1a0ed"}, "xml_json": {:hex, :xml_json, "0.4.2", "dfb8f34e5dc82b2642ea71bc92b853098f8d110367d40e26c634244bc2ebe04c", [:mix], [{:saxy, "~> 1.2", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "9102dfa0b2878a8808262d1602185543735acd933c978fb4c4a1b065c5f0ec79"}, } From 18a99c047bef93a126733bd1815b8a646cc79cb0 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:48:18 +0200 Subject: [PATCH 12/34] standardize --- lib/uof/api/descriptions/bet_stop.ex | 64 +++++++++------------- lib/uof/api/descriptions/betting_status.ex | 64 +++++++++------------- 2 files changed, 54 insertions(+), 74 deletions(-) diff --git a/lib/uof/api/descriptions/bet_stop.ex b/lib/uof/api/descriptions/bet_stop.ex index a050605..3c94efe 100644 --- a/lib/uof/api/descriptions/bet_stop.ex +++ b/lib/uof/api/descriptions/bet_stop.ex @@ -9,10 +9,29 @@ defmodule UOF.API.Descriptions.BetStop do provided in a subsequent `OddsChange` message. """ use Ecto.Schema - import Ecto.Changeset import UOF.API.EctoHelpers + @doc """ + List all supported bet stop reasons. + """ + @spec all() :: list(BetStop.t()) + def all() do + case UOF.API.get("/descriptions/betstop_reasons.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("betstop_reasons_descriptions") + |> Map.get("betstop_reason") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + @primary_key false embedded_schema do @@ -114,45 +133,16 @@ defmodule UOF.API.Descriptions.BetStop do ) end - @doc """ - List all supported bet stop reasons. - """ - @spec all() :: list(BetStop.t()) - def all() do - case UOF.API.get("/descriptions/betstop_reasons.xml") do - {:ok, %_{status: 200, body: resp}} -> - resp - |> Map.get("betstop_reasons_descriptions") - |> Map.get("betstop_reason") - |> Enum.map(fn x -> - {:ok, x} = cast(x) - x - end) - - {:error, _} = error -> - error - end - end + def changeset(model \\ %__MODULE__{}, params) do + params = prepare(params) - defp cast(params) do - %__MODULE__{} - |> cast(attrs(params), [:id, :description]) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} - - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end + model + |> cast(params, [:id, :description]) + |> apply end - defp attrs(params) do + defp prepare(params) do params - |> Enum.map(fn - {"@id", val} -> {:id, val} - {"@description", val} -> {:description, val} - x -> x - end) - |> Map.new() + |> rename_fields end end diff --git a/lib/uof/api/descriptions/betting_status.ex b/lib/uof/api/descriptions/betting_status.ex index 282de5f..a884a24 100644 --- a/lib/uof/api/descriptions/betting_status.ex +++ b/lib/uof/api/descriptions/betting_status.ex @@ -9,10 +9,29 @@ defmodule UOF.API.Descriptions.BettingStatus do no longer present in `OddsChange` messages. """ use Ecto.Schema - import Ecto.Changeset import UOF.API.EctoHelpers + @doc """ + List all supported betting statuses. + """ + @spec all() :: list(BettingStatus.t()) + def all() do + case UOF.API.get("/descriptions/betting_status.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("betting_status_descriptions") + |> Map.get("betting_status") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + @primary_key false embedded_schema do @@ -31,45 +50,16 @@ defmodule UOF.API.Descriptions.BettingStatus do ) end - def cast(params) do - %__MODULE__{} - |> cast(attrs(params), [:id, :description]) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} + def changeset(model \\ %__MODULE__{}, params) do + params = prepare(params) - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end + model + |> cast(params, [:id, :description]) + |> apply end - defp attrs(params) do + defp prepare(params) do params - |> Enum.map(fn - {"@id", val} -> {:id, val} - {"@description", val} -> {:description, val} - x -> x - end) - |> Map.new() - end - - @doc """ - List all supported betting statuses. - """ - @spec all() :: list(BettingStatus.t()) - def all() do - case UOF.API.get("/descriptions/betting_status.xml") do - {:ok, %_{status: 200, body: resp}} -> - resp - |> Map.get("betting_status_descriptions") - |> Map.get("betting_status") - |> Enum.map(fn x -> - {:ok, x} = cast(x) - x - end) - - {:error, _} = error -> - error - end + |> rename_fields end end From db39955b63901d646b63b1f0c6fbe3d0e33349ba Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 11 May 2024 18:57:26 +0200 Subject: [PATCH 13/34] standardize --- lib/uof/api/descriptions/match_status.ex | 83 ++++++++++-------------- 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/lib/uof/api/descriptions/match_status.ex b/lib/uof/api/descriptions/match_status.ex index 48a3a3b..afdcd8a 100644 --- a/lib/uof/api/descriptions/match_status.ex +++ b/lib/uof/api/descriptions/match_status.ex @@ -33,66 +33,55 @@ defmodule UOF.API.Descriptions.MatchStatus do end end - defmodule Sports do - @moduledoc false - use Ecto.Schema - import Ecto.Changeset - import UOF.API.EctoHelpers - - @primary_key false - - embedded_schema do - field(:all, :boolean, default: false) - field(:ids, {:array, :string}, default: []) - end + @primary_key false - def changeset(model \\ %__MODULE__{}, params) do - cast(model, prepare(params), [:all, :ids]) - end + embedded_schema do + field :id, :integer + field :description, :string + field :period_number, :integer - defp prepare(params) do - params - |> rename_fields - |> prepare_ids + embeds_one :sports, Sports, primary_key: false do + field :all, :boolean, default: false + field :ids, {:array, :string}, default: [] end + end - defp prepare_ids(params) do - ids = - params - |> Map.get("sport", []) - |> Enum.map(fn - {"@id", id} -> id - %{"@id" => id} -> id - end) + def changeset(model \\ %__MODULE__{}, params) do + params = prepare(params) - Map.put(params, "ids", ids) - end + model + |> cast(params, [:id, :description, :period_number]) + |> cast_embed(:sports, with: &sport_changeset/2) + |> apply end - @primary_key false - - embedded_schema do - field(:id, :integer) - field(:description, :string) - field(:period_number, :integer) - embeds_one(:sports, Sports) + defp prepare(params) do + params + |> rename_fields end - def changeset(model \\ %__MODULE__{}, params) do - model - |> cast(prepare(params), [:id, :description, :period_number]) - |> cast_embed(:sports) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} + def sport_changeset(model \\ %__MODULE__{}, params) do + params = prepare_sport(params) - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end + model + |> cast(params, [:all, :ids]) end - defp prepare(params) do + defp prepare_sport(params) do params |> rename_fields + |> prepare_ids + end + + defp prepare_ids(params) do + ids = + params + |> Map.get("sport", []) + |> Enum.map(fn + {"@id", id} -> id + %{"@id" => id} -> id + end) + + Map.put(params, "ids", ids) end end From a89a487620b01819bacd38e387d7eb52fe110b42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 11:36:51 +0000 Subject: [PATCH 14/34] standardize --- lib/uof/api/descriptions/bet_stop.ex | 7 +- lib/uof/api/descriptions/betting_status.ex | 7 +- lib/uof/api/descriptions/market.ex | 131 +++++---------------- lib/uof/api/descriptions/match_status.ex | 26 ++-- lib/uof/api/descriptions/producers.ex | 26 ++-- lib/uof/api/descriptions/variant.ex | 86 ++++---------- lib/uof/api/descriptions/void_reason.ex | 17 +-- lib/uof/api/ecto_helpers.ex | 17 ++- 8 files changed, 92 insertions(+), 225 deletions(-) diff --git a/lib/uof/api/descriptions/bet_stop.ex b/lib/uof/api/descriptions/bet_stop.ex index 3c94efe..8659cb5 100644 --- a/lib/uof/api/descriptions/bet_stop.ex +++ b/lib/uof/api/descriptions/bet_stop.ex @@ -134,15 +134,10 @@ defmodule UOF.API.Descriptions.BetStop do end def changeset(model \\ %__MODULE__{}, params) do - params = prepare(params) + params = sanitize(params) model |> cast(params, [:id, :description]) |> apply end - - defp prepare(params) do - params - |> rename_fields - end end diff --git a/lib/uof/api/descriptions/betting_status.ex b/lib/uof/api/descriptions/betting_status.ex index a884a24..f358411 100644 --- a/lib/uof/api/descriptions/betting_status.ex +++ b/lib/uof/api/descriptions/betting_status.ex @@ -51,15 +51,10 @@ defmodule UOF.API.Descriptions.BettingStatus do end def changeset(model \\ %__MODULE__{}, params) do - params = prepare(params) + params = sanitize(params) model |> cast(params, [:id, :description]) |> apply end - - defp prepare(params) do - params - |> rename_fields - end end diff --git a/lib/uof/api/descriptions/market.ex b/lib/uof/api/descriptions/market.ex index 4637739..0e0442b 100644 --- a/lib/uof/api/descriptions/market.ex +++ b/lib/uof/api/descriptions/market.ex @@ -51,121 +51,54 @@ defmodule UOF.API.Descriptions.Market do end end - defmodule Outcome do - @moduledoc false - use Ecto.Schema - import Ecto.Changeset - import UOF.API.EctoHelpers - - @primary_key false - - embedded_schema do - field(:id, :integer) - field(:name, :string) - end + @primary_key false - def changeset(model \\ %__MODULE__{}, params) do - cast(model, prepare(params), [:id, :name]) - end + embedded_schema do + field :id, :integer + field :name, :string + field :groups, {:array, :string} + # ["player", "competitor", "free_text"] + field :outcome_type, :string + field :includes_outcomes_of_type, :string - defp prepare(params) do - params - |> rename_fields + embeds_many :outcomes, Outcome, primary_key: false do + field :id, :integer + field :name, :string end - end - defmodule Specifier do - @moduledoc false - use Ecto.Schema - import Ecto.Changeset - import UOF.API.EctoHelpers - - @primary_key false - - embedded_schema do + embeds_many :specifiers, Specifier, primary_key: false do # ["decimal", "integer", "string", "variable_text"] - field(:type, :string) - field(:name, :string) - end - - def changeset(model \\ %__MODULE__{}, params) do - cast(model, prepare(params), [:name, :type]) + field :type, :string + field :name, :string end - defp prepare(params) do - params - |> rename_fields - end - end - - @primary_key false - - embedded_schema do - field(:id, :integer) - field(:name, :string) - field(:groups, {:array, :string}) - # ["player", "competitor", "free_text"] - field(:outcome_type, :string) - field(:includes_outcomes_of_type, :string) - embeds_many(:outcomes, Outcome) - embeds_many(:specifiers, Specifier) # TO-DO: embeds_many(:mappings, Mapping) end - def changeset(model \\ %__MODULE__{}, params) do - model - |> cast(prepare(params), [:id, :name, :groups, :outcome_type, :includes_outcomes_of_type]) - |> cast_embed(:outcomes) - |> cast_embed(:specifiers) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} + def changeset(model \\ %__MODULE__{}, params) - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end - end - - defp prepare(params) do - params - |> rename_fields - |> prepare_outcomes - |> prepare_specifiers - |> split_groups - end - - defp prepare_outcomes(params) do - outcomes = + def changeset(%__MODULE__{} = model, params) do + params = params - |> Map.get("outcomes", %{}) - |> Map.get("outcome", []) - - case outcomes do - outcome when not is_list(outcome) -> - Map.put(params, "outcomes", [outcome]) + |> sanitize + |> bubble_up("outcomes", "outcome") + |> bubble_up("specifiers", "specifier") + |> split("groups", "|") - _ -> - Map.put(params, "outcomes", outcomes) - end + model + |> cast(params, [:id, :name, :groups, :outcome_type, :includes_outcomes_of_type]) + |> cast_embed(:outcomes, with: &changeset/2) + |> cast_embed(:specifiers, with: &changeset/2) + |> apply end - defp prepare_specifiers(params) do - specifiers = - params - |> Map.get("specifiers", %{}) - |> Map.get("specifier", []) - - case specifiers do - specifier when not is_list(specifier) -> - Map.put(params, "specifiers", [specifier]) - - _ -> - Map.put(params, "specifiers", specifiers) - end + def changeset(%UOF.API.Descriptions.Market.Outcome{} = model, params) do + params = sanitize(params) + cast(model, params, [:id, :name]) end - defp split_groups(params) do - scope = String.split(params["groups"], "|") - Map.put(params, "groups", scope) + def changeset(%UOF.API.Descriptions.Market.Specifier{} = model, params) do + params = sanitize(params) + cast(model, params, [:type, :name]) end end diff --git a/lib/uof/api/descriptions/match_status.ex b/lib/uof/api/descriptions/match_status.ex index afdcd8a..8348593 100644 --- a/lib/uof/api/descriptions/match_status.ex +++ b/lib/uof/api/descriptions/match_status.ex @@ -46,33 +46,27 @@ defmodule UOF.API.Descriptions.MatchStatus do end end - def changeset(model \\ %__MODULE__{}, params) do - params = prepare(params) + def changeset(model \\ %__MODULE__{}, params) + + def changeset(%__MODULE__{} = model, params) do + params = sanitize(params) model |> cast(params, [:id, :description, :period_number]) - |> cast_embed(:sports, with: &sport_changeset/2) + |> cast_embed(:sports, with: &changeset/2) |> apply end - defp prepare(params) do - params - |> rename_fields - end - - def sport_changeset(model \\ %__MODULE__{}, params) do - params = prepare_sport(params) + def changeset(%UOF.API.Descriptions.MatchStatus.Sports{} = model, params) do + params = + params + |> sanitize + |> prepare_ids model |> cast(params, [:all, :ids]) end - defp prepare_sport(params) do - params - |> rename_fields - |> prepare_ids - end - defp prepare_ids(params) do ids = params diff --git a/lib/uof/api/descriptions/producers.ex b/lib/uof/api/descriptions/producers.ex index 5442447..593c423 100644 --- a/lib/uof/api/descriptions/producers.ex +++ b/lib/uof/api/descriptions/producers.ex @@ -41,8 +41,13 @@ defmodule UOF.API.Descriptions.Producer do end def changeset(model \\ %__MODULE__{}, params) do + params = + params + |> sanitize + |> split("scope", "|") + model - |> cast(prepare(params), [ + |> cast(params, [ :id, :name, :description, @@ -51,23 +56,6 @@ defmodule UOF.API.Descriptions.Producer do :scope, :stateful_recovery_window_in_minutes ]) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} - - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end - end - - defp prepare(params) do - params - |> rename_fields - |> prepare_scope - end - - defp prepare_scope(params) do - scope = String.split(params["scope"], "|") - Map.put(params, "scope", scope) + |> apply end end diff --git a/lib/uof/api/descriptions/variant.ex b/lib/uof/api/descriptions/variant.ex index afe6d3f..aa80d47 100644 --- a/lib/uof/api/descriptions/variant.ex +++ b/lib/uof/api/descriptions/variant.ex @@ -50,89 +50,45 @@ defmodule UOF.API.Descriptions.Variant do end end - def changeset(model \\ %__MODULE__{}, params) do - params = prepare(params) + def changeset(model \\ %__MODULE__{}, params) + + def changeset(%__MODULE__{} = model, params) do + params = + params + |> sanitize + |> bubble_up("outcomes", "outcome") + |> bubble_up("mappings", "mapping") model |> cast(params, [:id]) - |> cast_embed(:outcomes, with: &outcome_changeset/2) - |> cast_embed(:mappings, with: &mapping_changeset/2) + |> cast_embed(:outcomes, with: &changeset/2) + |> cast_embed(:mappings, with: &changeset/2) |> apply end - defp prepare(params) do - params - |> rename_fields - |> prepare_outcomes - |> prepare_mappings - end - - defp prepare_outcomes(params) do - outcomes = - params - |> Map.get("outcomes", %{}) - |> Map.get("outcome", []) - - case outcomes do - outcome when not is_list(outcome) -> - Map.put(params, "outcomes", [outcome]) - - _ -> - Map.put(params, "outcomes", outcomes) - end - end - - defp prepare_mappings(params) do - mappings = - params - |> Map.get("mappings", %{}) - |> Map.get("mapping", []) - - case mappings do - mapping when not is_list(mapping) -> - Map.put(params, "mappings", [mapping]) - - _ -> - Map.put(params, "mappings", mappings) - end - end - - def outcome_changeset(model, params) do - params = prepare_outcome(params) + def changeset(%UOF.API.Descriptions.Variant.Outcome{} = model, params) do + params = sanitize(params) model |> cast(params, [:id, :name]) end - def prepare_outcome(params) do - params - |> rename_fields - end - - def mapping_changeset(model, params) do - params = prepare_mapping(params) + def changeset(%UOF.API.Descriptions.Variant.Mapping{} = model, params) do + params = + params + |> sanitize + |> split("product_ids", "|") + |> rename("mapping_outcome", "outcome_mappings", []) model |> cast(params, [:product_id, :product_ids, :sport_id, :market_id, :product_market_id]) - |> cast_embed(:outcome_mappings, with: &outcome_mapping_changeset/2) - end - - def prepare_mapping(params) do - params - |> rename_fields - |> split("product_ids", "|") - |> rename("mapping_outcome", "outcome_mappings", []) + |> cast_embed(:outcome_mappings, with: &changeset/2) end - def outcome_mapping_changeset(model, params) do - params = prepare_outcome_mapping(params) + def changeset(%UOF.API.Descriptions.Variant.Mapping.OutcomeMapping{} = model, params) do + params = sanitize(params) model |> cast(params, [:outcome_id, :product_outcome_id, :product_outcome_name]) end - - def prepare_outcome_mapping(params) do - params - |> rename_fields - end end diff --git a/lib/uof/api/descriptions/void_reason.ex b/lib/uof/api/descriptions/void_reason.ex index b90ccdc..880a435 100644 --- a/lib/uof/api/descriptions/void_reason.ex +++ b/lib/uof/api/descriptions/void_reason.ex @@ -63,19 +63,10 @@ defmodule UOF.API.Descriptions.VoidReason do end def changeset(model \\ %__MODULE__{}, params) do - model - |> cast(prepare(params), [:id, :description]) - |> case do - %Ecto.Changeset{valid?: true} = changeset -> - {:ok, apply_changes(changeset)} + params = sanitize(params) - %Ecto.Changeset{} = changeset -> - {:error, {params, traverse_errors(changeset)}} - end - end - - defp prepare(params) do - params - |> rename_fields + model + |> cast(params, [:id, :description]) + |> apply end end diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex index 4859204..33587b3 100644 --- a/lib/uof/api/ecto_helpers.ex +++ b/lib/uof/api/ecto_helpers.ex @@ -20,7 +20,7 @@ defmodule UOF.API.EctoHelpers do @doc """ Remove leading '@' from field names in the `params` map. """ - def rename_fields(params) do + def sanitize(params) do params |> Enum.map(fn {<<"@", key::binary>>, value} -> {key, value} @@ -29,6 +29,21 @@ defmodule UOF.API.EctoHelpers do |> Map.new() end + def bubble_up(params, level1, level2) do + values = + params + |> Map.get(level1, %{}) + |> Map.get(level2, []) + + case values do + value when not is_list(value) -> + Map.put(params, level1, [value]) + + _ -> + Map.put(params, level1, values) + end + end + def split(params, field, separator) do values = Map.get(params, field) values = String.split(values, separator) From c69b89ae0090ea5bc5799da21864bf6c88eb7d4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 11:39:50 +0000 Subject: [PATCH 15/34] remove dead code --- lib/uof/api/mappings/variant.ex | 12 ------------ lib/uof/api/mappings/variant_descriptions.ex | 10 ---------- 2 files changed, 22 deletions(-) delete mode 100644 lib/uof/api/mappings/variant.ex delete mode 100644 lib/uof/api/mappings/variant_descriptions.ex diff --git a/lib/uof/api/mappings/variant.ex b/lib/uof/api/mappings/variant.ex deleted file mode 100644 index 85ac5ea..0000000 --- a/lib/uof/api/mappings/variant.ex +++ /dev/null @@ -1,12 +0,0 @@ -defmodule UOF.API.Mappings.Variant do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.{OutcomeMappings, Outcome} - - document do - attribute(:id) - elements(:outcome, as: :outcomes, into: %Outcome{}) - elements(:mapping, as: :mappings, into: %OutcomeMappings{}) - end -end diff --git a/lib/uof/api/mappings/variant_descriptions.ex b/lib/uof/api/mappings/variant_descriptions.ex deleted file mode 100644 index 5384041..0000000 --- a/lib/uof/api/mappings/variant_descriptions.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.VariantDescriptions do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Variant - - document do - elements(:variant, as: :variants, into: %Variant{}) - end -end From 867141e64fa2472afedfa60a05de313f3283f343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 11:44:14 +0000 Subject: [PATCH 16/34] remove dead code --- lib/uof/api/mappings/outcome_mapping.ex | 10 ---------- lib/uof/api/mappings/outcome_mappings.ex | 15 --------------- 2 files changed, 25 deletions(-) delete mode 100644 lib/uof/api/mappings/outcome_mapping.ex delete mode 100644 lib/uof/api/mappings/outcome_mappings.ex diff --git a/lib/uof/api/mappings/outcome_mapping.ex b/lib/uof/api/mappings/outcome_mapping.ex deleted file mode 100644 index 34fcaa0..0000000 --- a/lib/uof/api/mappings/outcome_mapping.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.OutcomeMapping do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:outcome_id) - attribute(:product_outcome_id) - attribute(:product_outcome_name) - end -end diff --git a/lib/uof/api/mappings/outcome_mappings.ex b/lib/uof/api/mappings/outcome_mappings.ex deleted file mode 100644 index 6ab8777..0000000 --- a/lib/uof/api/mappings/outcome_mappings.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule UOF.API.Mappings.OutcomeMappings do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.OutcomeMapping - - document do - attribute(:product_id, cast: :integer) - attribute(:product_ids) - attribute(:sport_id) - attribute(:market_id, cast: :integer) - attribute(:product_market_id) - elements(:mapping_outcome, as: :outcome_mappings, into: %OutcomeMapping{}) - end -end From 7f30d0879f4749e6f6f402f5e6699473d04c1fe2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 11:45:34 +0000 Subject: [PATCH 17/34] remove dead code --- lib/uof/api/mappings/outcome.ex | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 lib/uof/api/mappings/outcome.ex diff --git a/lib/uof/api/mappings/outcome.ex b/lib/uof/api/mappings/outcome.ex deleted file mode 100644 index 0b4da03..0000000 --- a/lib/uof/api/mappings/outcome.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule UOF.API.Mappings.Outcome do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id) - attribute(:name) - end -end From e836eac3386747e66066a7b8a38b408e70258122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 15:56:37 +0000 Subject: [PATCH 18/34] skip from documentation --- lib/uof/api/ecto_helpers.ex | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex index 33587b3..cc70616 100644 --- a/lib/uof/api/ecto_helpers.ex +++ b/lib/uof/api/ecto_helpers.ex @@ -1,8 +1,5 @@ defmodule UOF.API.EctoHelpers do - @moduledoc """ - Ecto Helpers is a module that is ensuring we have the common functions in - use for most of the schemas or modules that uses Ecto. - """ + @moduledoc false import Ecto.Changeset @doc """ From 155c3bb2fea766575673009e59c7b6572b10e826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20Fern=C3=A1ndez?= Date: Sun, 12 May 2024 16:06:14 +0000 Subject: [PATCH 19/34] user --- lib/uof/api/mappings/bookmaker_details.ex | 15 ------------ lib/uof/api/users.ex | 30 ++++++++++++++++++++--- test/uof/api/users_test.exs | 20 +++++++-------- 3 files changed, 36 insertions(+), 29 deletions(-) delete mode 100644 lib/uof/api/mappings/bookmaker_details.ex diff --git a/lib/uof/api/mappings/bookmaker_details.ex b/lib/uof/api/mappings/bookmaker_details.ex deleted file mode 100644 index 25e4913..0000000 --- a/lib/uof/api/mappings/bookmaker_details.ex +++ /dev/null @@ -1,15 +0,0 @@ -defmodule UOF.API.Mappings.BookmakerDetails do - @moduledoc false - use Saxaboom.Mapper - - @type t :: %__MODULE__{ - expire_at: String.t(), - bookmaker_id: String.t(), - virtual_host: String.t() - } - document do - attribute(:expire_at) - attribute(:bookmaker_id) - attribute(:virtual_host) - end -end diff --git a/lib/uof/api/users.ex b/lib/uof/api/users.ex index 868cbdb..f706e8a 100644 --- a/lib/uof/api/users.ex +++ b/lib/uof/api/users.ex @@ -2,15 +2,39 @@ defmodule UOF.API.Users do @moduledoc """ API used for administrative purposes. """ - alias UOF.API.Utils.HTTP + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers @doc """ Get information about the token being used, including information such as the caller's bookmaker id and when the caller's access token will expire. """ def whoami do - endpoint = ["users", "whoami.xml"] + case UOF.API.get("/users/whoami.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("bookmaker_details") + |> changeset - HTTP.get(%UOF.API.Mappings.BookmakerDetails{}, endpoint) + {:error, _} = error -> + error + end + end + + @primary_key false + + embedded_schema do + field :expire_at, :string + field :bookmaker_id, :string + field :virtual_host, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + params = sanitize(params) + + model + |> cast(params, [:expire_at, :bookmaker_id, :virtual_host]) + |> apply end end diff --git a/test/uof/api/users_test.exs b/test/uof/api/users_test.exs index 0c2b5d0..175fce1 100644 --- a/test/uof/api/users_test.exs +++ b/test/uof/api/users_test.exs @@ -1,17 +1,15 @@ defmodule UOF.API.Users.Test do use ExUnit.Case - import Mock - # mock http requests towards Betradar and use recorded responses instead - setup_with_mocks([ - {UOF.API.Utils.HTTP, [:passthrough], - [ - get: fn schema, endpoint -> - data = File.read!("test/data/" <> Enum.at(endpoint, -1)) - Saxaboom.parse(data, schema) - end - ]} - ]) do + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/whoami.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + :ok end From e7b267823454137ad19397ab8e0fedb968d53e26 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 12 May 2024 18:57:45 +0200 Subject: [PATCH 20/34] sport categories --- lib/uof/api/mappings/sport_categories.ex | 11 --- lib/uof/api/sports.ex | 9 --- lib/uof/api/sports/category.ex | 85 ++++++++++++++++++++++++ test/uof/api/sports/category_test.exs | 31 +++++++++ test/uof/api/sports_test.exs | 20 ------ 5 files changed, 116 insertions(+), 40 deletions(-) delete mode 100644 lib/uof/api/mappings/sport_categories.ex create mode 100644 lib/uof/api/sports/category.ex create mode 100644 test/uof/api/sports/category_test.exs diff --git a/lib/uof/api/mappings/sport_categories.ex b/lib/uof/api/mappings/sport_categories.ex deleted file mode 100644 index 6ea1812..0000000 --- a/lib/uof/api/mappings/sport_categories.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule UOF.API.Mappings.SportCategories do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.{Category, Sport} - - document do - element(:sport, into: %Sport{}) - elements(:category, as: :categories, into: %Category{}) - end -end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index c564424..c45a213 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -139,15 +139,6 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Sports{}, endpoint) end - @doc """ - List all the available categories for the given sport. - """ - def categories(sport, lang \\ "en") do - endpoint = ["sports", lang, "sports", sport, "categories.xml"] - - HTTP.get(%UOF.API.Mappings.SportCategories{}, endpoint) - end - def tournaments(lang \\ "en") do endpoint = ["sports", lang, "tournaments.xml"] diff --git a/lib/uof/api/sports/category.ex b/lib/uof/api/sports/category.ex new file mode 100644 index 0000000..976dcd7 --- /dev/null +++ b/lib/uof/api/sports/category.ex @@ -0,0 +1,85 @@ +# https://docs.betradar.com/pages/viewpage.action?pageId=116065083 +defmodule UOF.API.Sports.Category do + @moduledoc """ + Categories are a generic classification term Betradar uses to subclassify a + particular sport (e.g. for Tennis the categories can be ATP-Tour, WTA-Tour, + David Cup, etc., for soccer the categories are the various countries). + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all the available categories for the given sport. + """ + @spec by_sport(sport :: String.t(), lang :: String.t()) :: list(__MODULE__.t()) + def by_sport(sport, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/sports/#{sport}/categories.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("sport_categories") + |> changeset + + {:error, _} = error -> + error + end + end + + @type sport :: %UOF.API.Sports.Category.Sport{ + id: String.t(), + name: String.t() + } + + @type category :: %UOF.API.Sports.Category.Category{ + id: String.t(), + name: String.t(), + country_code: String.t() + } + + @type t :: %__MODULE__{ + sport: sport(), + categories: list(category()) + } + + @primary_key false + + embedded_schema do + embeds_one :sport, Sport, primary_key: false do + field :id, :string + field :name, :string + end + + embeds_many :categories, Category, primary_key: false do + field :id, :string + field :name, :string + field :country_code, :string + end + end + + @doc false + def changeset(model \\ %__MODULE__{}, params) + + def changeset(%__MODULE__{} = model, params) do + params = bubble_up(params, "categories", "category") + + model + |> cast(params, []) + |> cast_embed(:sport, with: &changeset/2) + |> cast_embed(:categories, with: &changeset/2) + |> apply + end + + def changeset(%UOF.API.Sports.Category.Sport{} = model, params) do + params = sanitize(params) + + model + |> cast(params, [:id, :name]) + end + + def changeset(%UOF.API.Sports.Category.Category{} = model, params) do + params = sanitize(params) + + model + |> cast(params, [:id, :name, :country_code]) + end +end diff --git a/test/uof/api/sports/category_test.exs b/test/uof/api/sports/category_test.exs new file mode 100644 index 0000000..9b9a08e --- /dev/null +++ b/test/uof/api/sports/category_test.exs @@ -0,0 +1,31 @@ +defmodule UOF.API.Sports.Category.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/categories.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Sports.Category.by_sport/{1, 2} response" do + {:ok, sport_categories} = UOF.API.Sports.Category.by_sport("sr:sport:1") + + # sport + sport = sport_categories.sport + assert sport.id == "sr:sport:1" + assert sport.name == "Soccer" + # categories + categories = sport_categories.categories + category = hd(categories) + assert Enum.count(categories) == 224 + assert category.id == "sr:category:1" + assert category.name == "England" + assert category.country_code == "ENG" + end +end diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index c9b8641..7842346 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -35,10 +35,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/timeline.xml")} end - defp fetch_mock_data(["sports", _lang, "sports", _sport, "categories.xml"]) do - {:ok, File.read!("test/data/categories.xml")} - end - defp fetch_mock_data(["sports", _lang, "tournaments.xml"]) do {:ok, File.read!("test/data/tournaments.xml")} end @@ -422,22 +418,6 @@ defmodule UOF.API.Sports.Test do assert match_ended_event.match_clock == "60:00" end - test "can parse UOF.API.Sports.categories/{1, 2} response" do - {:ok, sport_categories} = UOF.API.Sports.categories("sr:sport:1") - - # sport - sport = sport_categories.sport - assert sport.id == "sr:sport:1" - assert sport.name == "Soccer" - # categories - categories = sport_categories.categories - category = hd(categories) - assert Enum.count(categories) == 224 - assert category.id == "sr:category:1" - assert category.name == "England" - assert category.country_code == "ENG" - end - test "can parse UOF.API.Sports.tournaments/{0, 1} response" do {:ok, data} = UOF.API.Sports.tournaments() From 93422ad6fa07428391ef64a648eacba568a13b1e Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 12 May 2024 19:13:27 +0200 Subject: [PATCH 21/34] sports --- lib/uof/api/mappings/sports.ex | 10 ------- lib/uof/api/sports.ex | 9 ------ lib/uof/api/sports/sport.ex | 47 ++++++++++++++++++++++++++++++ test/uof/api/sports/sport_test.exs | 23 +++++++++++++++ test/uof/api/sports_test.exs | 14 --------- 5 files changed, 70 insertions(+), 33 deletions(-) delete mode 100644 lib/uof/api/mappings/sports.ex create mode 100644 lib/uof/api/sports/sport.ex create mode 100644 test/uof/api/sports/sport_test.exs diff --git a/lib/uof/api/mappings/sports.ex b/lib/uof/api/mappings/sports.ex deleted file mode 100644 index b63ae9c..0000000 --- a/lib/uof/api/mappings/sports.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.Sports do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Sport - - document do - elements(:sport, as: :sports, into: %Sport{}) - end -end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index c45a213..dfab5f3 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -130,15 +130,6 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Timeline{}, endpoint) end - @doc """ - List all the available sports. - """ - def sports(lang \\ "en") do - endpoint = ["sports", lang, "sports.xml"] - - HTTP.get(%UOF.API.Mappings.Sports{}, endpoint) - end - def tournaments(lang \\ "en") do endpoint = ["sports", lang, "tournaments.xml"] diff --git a/lib/uof/api/sports/sport.ex b/lib/uof/api/sports/sport.ex new file mode 100644 index 0000000..7ae491e --- /dev/null +++ b/lib/uof/api/sports/sport.ex @@ -0,0 +1,47 @@ +defmodule UOF.API.Sports.Sport do + @moduledoc """ + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all the available sports. + """ + @spec all(lang :: String.t()) :: list(__MODULE__.t()) + def all(lang \\ "en") do + case UOF.API.get("/sports/#{lang}/sports.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> Map.get("sports") + |> Map.get("sport") + |> Enum.map(fn x -> + {:ok, x} = changeset(x) + x + end) + + {:error, _} = error -> + error + end + end + + @type t :: %__MODULE__{ + id: String.t(), + name: String.t() + } + + @primary_key false + + embedded_schema do + field :id, :string + field :name, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + params = sanitize(params) + + model + |> cast(params, [:id, :name]) + |> apply + end +end diff --git a/test/uof/api/sports/sport_test.exs b/test/uof/api/sports/sport_test.exs new file mode 100644 index 0000000..17d15c9 --- /dev/null +++ b/test/uof/api/sports/sport_test.exs @@ -0,0 +1,23 @@ +defmodule UOF.API.Sports.Sport.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/sports.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Sports.Sport.all/{0, 1} response" do + sports = UOF.API.Sports.Sport.all() + + assert Enum.count(sports) == 204 + assert hd(sports).id == "sr:sport:143" + assert hd(sports).name == "7BallRun" + end +end diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index 7842346..cea582c 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -39,10 +39,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/tournaments.xml")} end - defp fetch_mock_data(["sports", _lang, "sports.xml"]) do - {:ok, File.read!("test/data/sports.xml")} - end - defp fetch_mock_data(["sports", _lang, "tournaments", _tournament, "info.xml"]) do {:ok, File.read!("test/data/tournament_info.xml")} end @@ -451,16 +447,6 @@ defmodule UOF.API.Sports.Test do assert season_coverage.min_coverage_level == "silver" end - test "can parse UOF.API.Sports.sports/{0, 1} response" do - {:ok, data} = UOF.API.Sports.sports() - - sport = hd(data.sports) - - assert Enum.count(data.sports) == 204 - assert sport.id == "sr:sport:143" - assert sport.name == "7BallRun" - end - test "can parse UOF.API.Sports.tournament/{1, 2} response" do {:ok, info} = UOF.API.Sports.tournament("sr:tournament:7") From 9c927db3d9f8a42edcac485850672ef7f7758368 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Mon, 20 May 2024 19:48:07 +0200 Subject: [PATCH 22/34] use saxy simpleform --- config/test.exs | 5 + lib/uof/api.ex | 3 +- lib/uof/api/descriptions/bet_stop.ex | 3 +- lib/uof/api/descriptions/betting_status.ex | 3 +- lib/uof/api/descriptions/market.ex | 5 +- lib/uof/api/descriptions/match_status.ex | 12 +- lib/uof/api/descriptions/producers.ex | 6 +- lib/uof/api/descriptions/variant.ex | 7 +- lib/uof/api/descriptions/void_reason.ex | 3 +- lib/uof/api/ecto_helpers.ex | 12 -- lib/uof/api/mappings/tournaments.ex | 10 -- lib/uof/api/sports.ex | 18 --- lib/uof/api/sports/category.ex | 7 +- lib/uof/api/sports/sport.ex | 9 +- lib/uof/api/sports/tournament.ex | 132 ++++++++++++++++++ lib/uof/api/tournaments/coverage_info.ex | 18 +++ lib/uof/api/tournaments/group.ex | 69 +++++++++ lib/uof/api/tournaments/round.ex | 20 +++ lib/uof/api/tournaments/season.ex | 20 +++ .../api/tournaments/season_coverage_info.ex | 27 ++++ lib/uof/api/tournaments/tournament_info.ex | 33 +++++ lib/uof/api/users.ex | 3 +- lib/uof/api/utils.ex | 51 +++++++ mix.exs | 2 +- mix.lock | 8 +- test/uof/api/sports/tournament_test.exs | 102 ++++++++++++++ test/uof/api/sports_test.exs | 90 ------------ 27 files changed, 502 insertions(+), 176 deletions(-) create mode 100644 config/test.exs delete mode 100644 lib/uof/api/mappings/tournaments.ex create mode 100644 lib/uof/api/sports/tournament.ex create mode 100644 lib/uof/api/tournaments/coverage_info.ex create mode 100644 lib/uof/api/tournaments/group.ex create mode 100644 lib/uof/api/tournaments/round.ex create mode 100644 lib/uof/api/tournaments/season.ex create mode 100644 lib/uof/api/tournaments/season_coverage_info.ex create mode 100644 lib/uof/api/tournaments/tournament_info.ex create mode 100644 lib/uof/api/utils.ex create mode 100644 test/uof/api/sports/tournament_test.exs diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..90b05d2 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,5 @@ +import Config + +config :uof_api, + base_url: "https://localhost", + auth_token: "a-secret-goes-here" diff --git a/lib/uof/api.ex b/lib/uof/api.ex index 1dd2552..035f875 100644 --- a/lib/uof/api.ex +++ b/lib/uof/api.ex @@ -12,7 +12,8 @@ defmodule UOF.API do {"x-access-token", Application.get_env(:uof_api, :auth_token)} ]) - plug(Tesla.Middleware.XML, convention: BadgerFish) + # plug(Tesla.Middleware.XML, convention: BadgerFish) + plug(Tesla.Middleware.XML, engine: Saxy) # https://docs.betradar.com/display/BD/UOF+-+Language+Support @supported_languages [ diff --git a/lib/uof/api/descriptions/bet_stop.ex b/lib/uof/api/descriptions/bet_stop.ex index 8659cb5..92f5972 100644 --- a/lib/uof/api/descriptions/bet_stop.ex +++ b/lib/uof/api/descriptions/bet_stop.ex @@ -20,6 +20,7 @@ defmodule UOF.API.Descriptions.BetStop do case UOF.API.get("/descriptions/betstop_reasons.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("betstop_reasons_descriptions") |> Map.get("betstop_reason") |> Enum.map(fn x -> @@ -134,8 +135,6 @@ defmodule UOF.API.Descriptions.BetStop do end def changeset(model \\ %__MODULE__{}, params) do - params = sanitize(params) - model |> cast(params, [:id, :description]) |> apply diff --git a/lib/uof/api/descriptions/betting_status.ex b/lib/uof/api/descriptions/betting_status.ex index f358411..a195ab6 100644 --- a/lib/uof/api/descriptions/betting_status.ex +++ b/lib/uof/api/descriptions/betting_status.ex @@ -20,6 +20,7 @@ defmodule UOF.API.Descriptions.BettingStatus do case UOF.API.get("/descriptions/betting_status.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("betting_status_descriptions") |> Map.get("betting_status") |> Enum.map(fn x -> @@ -51,8 +52,6 @@ defmodule UOF.API.Descriptions.BettingStatus do end def changeset(model \\ %__MODULE__{}, params) do - params = sanitize(params) - model |> cast(params, [:id, :description]) |> apply diff --git a/lib/uof/api/descriptions/market.ex b/lib/uof/api/descriptions/market.ex index 0e0442b..38bdd5f 100644 --- a/lib/uof/api/descriptions/market.ex +++ b/lib/uof/api/descriptions/market.ex @@ -17,6 +17,7 @@ defmodule UOF.API.Descriptions.Market do ) do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("market_descriptions") |> Map.get("market") |> Enum.map(fn x -> @@ -38,6 +39,7 @@ defmodule UOF.API.Descriptions.Market do ) do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("market_descriptions") |> Map.get("market") @@ -80,7 +82,6 @@ defmodule UOF.API.Descriptions.Market do def changeset(%__MODULE__{} = model, params) do params = params - |> sanitize |> bubble_up("outcomes", "outcome") |> bubble_up("specifiers", "specifier") |> split("groups", "|") @@ -93,12 +94,10 @@ defmodule UOF.API.Descriptions.Market do end def changeset(%UOF.API.Descriptions.Market.Outcome{} = model, params) do - params = sanitize(params) cast(model, params, [:id, :name]) end def changeset(%UOF.API.Descriptions.Market.Specifier{} = model, params) do - params = sanitize(params) cast(model, params, [:type, :name]) end end diff --git a/lib/uof/api/descriptions/match_status.ex b/lib/uof/api/descriptions/match_status.ex index 8348593..7a9d0b0 100644 --- a/lib/uof/api/descriptions/match_status.ex +++ b/lib/uof/api/descriptions/match_status.ex @@ -21,6 +21,7 @@ defmodule UOF.API.Descriptions.MatchStatus do case UOF.API.get("/descriptions/#{lang}/match_status.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("match_status_descriptions") |> Map.get("match_status") |> Enum.map(fn x -> @@ -49,8 +50,6 @@ defmodule UOF.API.Descriptions.MatchStatus do def changeset(model \\ %__MODULE__{}, params) def changeset(%__MODULE__{} = model, params) do - params = sanitize(params) - model |> cast(params, [:id, :description, :period_number]) |> cast_embed(:sports, with: &changeset/2) @@ -58,10 +57,7 @@ defmodule UOF.API.Descriptions.MatchStatus do end def changeset(%UOF.API.Descriptions.MatchStatus.Sports{} = model, params) do - params = - params - |> sanitize - |> prepare_ids + params = prepare_ids(params) model |> cast(params, [:all, :ids]) @@ -72,8 +68,8 @@ defmodule UOF.API.Descriptions.MatchStatus do params |> Map.get("sport", []) |> Enum.map(fn - {"@id", id} -> id - %{"@id" => id} -> id + {"id", id} -> id + %{"id" => id} -> id end) Map.put(params, "ids", ids) diff --git a/lib/uof/api/descriptions/producers.ex b/lib/uof/api/descriptions/producers.ex index 593c423..6cf3df7 100644 --- a/lib/uof/api/descriptions/producers.ex +++ b/lib/uof/api/descriptions/producers.ex @@ -16,6 +16,7 @@ defmodule UOF.API.Descriptions.Producer do case UOF.API.get("/descriptions/producers.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("producers") |> Map.get("producer") |> Enum.map(fn x -> @@ -41,10 +42,7 @@ defmodule UOF.API.Descriptions.Producer do end def changeset(model \\ %__MODULE__{}, params) do - params = - params - |> sanitize - |> split("scope", "|") + params = split(params, "scope", "|") model |> cast(params, [ diff --git a/lib/uof/api/descriptions/variant.ex b/lib/uof/api/descriptions/variant.ex index aa80d47..06be3eb 100644 --- a/lib/uof/api/descriptions/variant.ex +++ b/lib/uof/api/descriptions/variant.ex @@ -13,6 +13,7 @@ defmodule UOF.API.Descriptions.Variant do case UOF.API.get("/descriptions/#{lang}/variants.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("variant_descriptions") |> Map.get("variant") |> Enum.map(fn x -> @@ -55,7 +56,6 @@ defmodule UOF.API.Descriptions.Variant do def changeset(%__MODULE__{} = model, params) do params = params - |> sanitize |> bubble_up("outcomes", "outcome") |> bubble_up("mappings", "mapping") @@ -67,8 +67,6 @@ defmodule UOF.API.Descriptions.Variant do end def changeset(%UOF.API.Descriptions.Variant.Outcome{} = model, params) do - params = sanitize(params) - model |> cast(params, [:id, :name]) end @@ -76,7 +74,6 @@ defmodule UOF.API.Descriptions.Variant do def changeset(%UOF.API.Descriptions.Variant.Mapping{} = model, params) do params = params - |> sanitize |> split("product_ids", "|") |> rename("mapping_outcome", "outcome_mappings", []) @@ -86,8 +83,6 @@ defmodule UOF.API.Descriptions.Variant do end def changeset(%UOF.API.Descriptions.Variant.Mapping.OutcomeMapping{} = model, params) do - params = sanitize(params) - model |> cast(params, [:outcome_id, :product_outcome_id, :product_outcome_name]) end diff --git a/lib/uof/api/descriptions/void_reason.ex b/lib/uof/api/descriptions/void_reason.ex index 880a435..d1d0991 100644 --- a/lib/uof/api/descriptions/void_reason.ex +++ b/lib/uof/api/descriptions/void_reason.ex @@ -19,6 +19,7 @@ defmodule UOF.API.Descriptions.VoidReason do case UOF.API.get("/descriptions/void_reasons.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("void_reasons_descriptions") |> Map.get("void_reason") |> Enum.map(fn x -> @@ -63,8 +64,6 @@ defmodule UOF.API.Descriptions.VoidReason do end def changeset(model \\ %__MODULE__{}, params) do - params = sanitize(params) - model |> cast(params, [:id, :description]) |> apply diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex index cc70616..03e5fc2 100644 --- a/lib/uof/api/ecto_helpers.ex +++ b/lib/uof/api/ecto_helpers.ex @@ -14,18 +14,6 @@ defmodule UOF.API.EctoHelpers do end) end - @doc """ - Remove leading '@' from field names in the `params` map. - """ - def sanitize(params) do - params - |> Enum.map(fn - {<<"@", key::binary>>, value} -> {key, value} - other -> other - end) - |> Map.new() - end - def bubble_up(params, level1, level2) do values = params diff --git a/lib/uof/api/mappings/tournaments.ex b/lib/uof/api/mappings/tournaments.ex deleted file mode 100644 index 1a3dce8..0000000 --- a/lib/uof/api/mappings/tournaments.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.Tournaments do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Tournament - - document do - elements(:tournament, as: :tournaments, into: %Tournament{}) - end -end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index dfab5f3..8cb7848 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -130,24 +130,6 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Timeline{}, endpoint) end - def tournaments(lang \\ "en") do - endpoint = ["sports", lang, "tournaments.xml"] - - HTTP.get(%UOF.API.Mappings.Tournaments{}, endpoint) - end - - @doc """ - Get details about the given tournament. - """ - def tournament(tournament, lang \\ "en") do - # https://docs.betradar.com/display/BD/UOF+-+Tournament+we+provide+coverage+for - endpoint = ["sports", lang, "tournaments", tournament, "info.xml"] - - # TO-DO: staged tournaments - # https://docs.betradar.com/display/BD/UOF+-+Formula+1 - HTTP.get(%UOF.API.Mappings.TournamentInfo{}, endpoint) - end - ## Entity Description ## ========================================================================= @doc """ diff --git a/lib/uof/api/sports/category.ex b/lib/uof/api/sports/category.ex index 976dcd7..bba57e9 100644 --- a/lib/uof/api/sports/category.ex +++ b/lib/uof/api/sports/category.ex @@ -17,8 +17,10 @@ defmodule UOF.API.Sports.Category do case UOF.API.get("/sports/#{lang}/sports/#{sport}/categories.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("sport_categories") |> changeset + |> apply {:error, _} = error -> error @@ -66,19 +68,14 @@ defmodule UOF.API.Sports.Category do |> cast(params, []) |> cast_embed(:sport, with: &changeset/2) |> cast_embed(:categories, with: &changeset/2) - |> apply end def changeset(%UOF.API.Sports.Category.Sport{} = model, params) do - params = sanitize(params) - model |> cast(params, [:id, :name]) end def changeset(%UOF.API.Sports.Category.Category{} = model, params) do - params = sanitize(params) - model |> cast(params, [:id, :name, :country_code]) end diff --git a/lib/uof/api/sports/sport.ex b/lib/uof/api/sports/sport.ex index 7ae491e..530e21d 100644 --- a/lib/uof/api/sports/sport.ex +++ b/lib/uof/api/sports/sport.ex @@ -13,10 +13,11 @@ defmodule UOF.API.Sports.Sport do case UOF.API.get("/sports/#{lang}/sports.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("sports") |> Map.get("sport") |> Enum.map(fn x -> - {:ok, x} = changeset(x) + {:ok, x} = apply(changeset(x)) x end) @@ -38,10 +39,6 @@ defmodule UOF.API.Sports.Sport do end def changeset(model \\ %__MODULE__{}, params) do - params = sanitize(params) - - model - |> cast(params, [:id, :name]) - |> apply + cast(model, params, [:id, :name]) end end diff --git a/lib/uof/api/sports/tournament.ex b/lib/uof/api/sports/tournament.ex new file mode 100644 index 0000000..a327bf9 --- /dev/null +++ b/lib/uof/api/sports/tournament.ex @@ -0,0 +1,132 @@ +# https://docs.betradar.com/display/BD/UOF+-+Tournament+we+provide+coverage+for +defmodule UOF.API.Sports.Tournament do + @moduledoc """ + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all the available tournaments. + """ + @spec all(lang :: String.t()) :: list(__MODULE__.t()) + def all(lang \\ "en") do + case UOF.API.get("/sports/#{lang}/tournaments.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("tournaments") + |> Map.get("tournament") + |> Enum.map(fn x -> + {:ok, x} = apply(changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end + + @doc """ + Show the details of the given tournament. + """ + def show(tournament, lang \\ "en") do + # TO-DO: staged tournaments + # https://docs.betradar.com/display/BD/UOF+-+Formula+1 + case UOF.API.get("/sports/#{lang}/tournaments/#{tournament}/info.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> UOF.API.Tournaments.TournamentInfo.changeset() + |> apply + + {:error, _} = error -> + error + end + end + + @type current_season :: %UOF.API.Sports.Tournament.CurrentSeason{ + start_date: String.t(), + end_date: String.t(), + year: String.t(), + id: String.t(), + name: String.t() + } + + @type season_coverage_info :: %UOF.API.Sports.Tournament.SeasonCoverageInfo{ + season_id: String.t(), + scheduled: integer(), + played: integer(), + max_coverage_level: String.t(), + max_covered: integer(), + min_coverage_level: String.t() + } + + @type t :: %__MODULE__{ + id: String.t(), + name: String.t(), + sport: UOF.API.Sports.Sport.t(), + category: UOF.API.Sports.Category.t(), + current_season: current_season(), + season_coverage_info: season_coverage_info() + } + + @primary_key false + + embedded_schema do + field :id, :string + field :name, :string + + embeds_one :current_season, CurrentSeason, primary_key: false do + field :start_date, :string + field :end_date, :string + field :year, :string + field :id, :string + field :name, :string + end + + embeds_one :season_coverage_info, SeasonCoverageInfo, primary_key: false do + field :season_id, :string + field :scheduled, :integer + field :played, :integer + field :max_coverage_level, :string + field :max_covered, :integer + field :min_coverage_level, :string + end + + embeds_one :sport, UOF.API.Sports.Sport + embeds_one :category, UOF.API.Sports.Category.Category + end + + def from_simple_form({element, attrs, elements}) do + %{element => Map.new(attrs ++ elements)} + end + + def changeset(model \\ %__MODULE__{}, params) + + def changeset(%__MODULE__{} = model, params) do + model + |> cast(params, [:id, :name]) + |> cast_embed(:sport) + |> cast_embed(:category, with: &UOF.API.Sports.Category.changeset/2) + |> cast_embed(:current_season, with: &changeset/2) + |> cast_embed(:season_coverage_info, with: &changeset/2) + end + + def changeset(%UOF.API.Sports.Tournament.CurrentSeason{} = model, params) do + model + |> cast(params, [:id, :name, :start_date, :end_date, :year]) + end + + def changeset(%UOF.API.Sports.Tournament.SeasonCoverageInfo{} = model, params) do + model + |> cast(params, [ + :season_id, + :scheduled, + :played, + :max_coverage_level, + :max_covered, + :min_coverage_level + ]) + end +end diff --git a/lib/uof/api/tournaments/coverage_info.ex b/lib/uof/api/tournaments/coverage_info.ex new file mode 100644 index 0000000..99569a7 --- /dev/null +++ b/lib/uof/api/tournaments/coverage_info.ex @@ -0,0 +1,18 @@ +defmodule UOF.API.Tournaments.CoverageInfo do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field :level, :string + field :live_coverage, :boolean + field :covered_from, :string + # TO-DO: Inlcudes + end + + def changeset(%__MODULE__{} = model, params) do + model + |> cast(params, [:level, :live_coverage, :covered_from]) + end +end diff --git a/lib/uof/api/tournaments/group.ex b/lib/uof/api/tournaments/group.ex new file mode 100644 index 0000000..e9c04d6 --- /dev/null +++ b/lib/uof/api/tournaments/group.ex @@ -0,0 +1,69 @@ +defmodule UOF.API.Tournaments.Group do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + # Example. sr:tournament:1211 + embedded_schema do + field :id, :string + field :name, :string + + embeds_many :competitors, Competitor, primary_key: false do + field :id, :string + field :name, :string + field :state, :string + field :country, :string + field :country_code, :string + field :abbreviation, :string + field :qualifier, :string + field :virtual, :string + field :gender, :string + field :short_name, :string + + embeds_many :references, Reference, primary_key: false do + field :name, :string + field :value, :string + end + end + end + + def changeset(model \\ %__MODULE__{}, params) + + def changeset(%__MODULE__{} = model, params) do + params = rename(params, "competitor", "competitors", []) + + model + |> cast(params, [:id, :name]) + |> cast_embed(:competitors, with: &changeset/2) + end + + def changeset(%UOF.API.Tournaments.Group.Competitor{} = model, params) do + params = + params + |> bubble_up("reference_ids", "reference_id") + |> rename("reference_ids", "references", []) + + model + |> cast(params, [ + :id, + :name, + :state, + :country, + :country_code, + :abbreviation, + :qualifier, + :virtual, + :gender, + :short_name + ]) + |> cast_embed(:references, with: &changeset/2) + end + + def changeset(%UOF.API.Tournaments.Group.Competitor.Reference{} = model, params) do + model + |> cast(params, [:name, :value]) + end +end diff --git a/lib/uof/api/tournaments/round.ex b/lib/uof/api/tournaments/round.ex new file mode 100644 index 0000000..6c788e5 --- /dev/null +++ b/lib/uof/api/tournaments/round.ex @@ -0,0 +1,20 @@ +defmodule UOF.API.Tournaments.Round do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :type, :string + field :name, :string + field :number, :integer + field :cup_round_matches, :integer + field :cup_round_match_number, :integer + end + + def changeset(%__MODULE__{} = model, params) do + model + |> cast(params, [:type, :name, :number, :cup_round_matches, :cup_round_match_number]) + end +end diff --git a/lib/uof/api/tournaments/season.ex b/lib/uof/api/tournaments/season.ex new file mode 100644 index 0000000..b3fa6ad --- /dev/null +++ b/lib/uof/api/tournaments/season.ex @@ -0,0 +1,20 @@ +defmodule UOF.API.Tournaments.Season do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :start_date, :string + field :end_date, :string + field :year, :string + field :id, :string + field :name, :string + end + + def changeset(%__MODULE__{} = model, params) do + model + |> cast(params, [:id, :name, :start_date, :end_date, :year]) + end +end diff --git a/lib/uof/api/tournaments/season_coverage_info.ex b/lib/uof/api/tournaments/season_coverage_info.ex new file mode 100644 index 0000000..ae4b78f --- /dev/null +++ b/lib/uof/api/tournaments/season_coverage_info.ex @@ -0,0 +1,27 @@ +defmodule UOF.API.Tournaments.SeasonCoverageInfo do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + embedded_schema do + field :season_id, :string + field :scheduled, :integer + field :played, :integer + field :max_coverage_level, :string + field :max_covered, :integer + field :min_coverage_level, :string + end + + def changeset(%__MODULE__{} = model, params) do + model + |> cast(params, [ + :season_id, + :scheduled, + :played, + :max_coverage_level, + :max_covered, + :min_coverage_level + ]) + end +end diff --git a/lib/uof/api/tournaments/tournament_info.ex b/lib/uof/api/tournaments/tournament_info.ex new file mode 100644 index 0000000..c418faf --- /dev/null +++ b/lib/uof/api/tournaments/tournament_info.ex @@ -0,0 +1,33 @@ +defmodule UOF.API.Tournaments.TournamentInfo do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + # Example. sr:tournament:1211 + embedded_schema do + # nested schema instead? + embeds_one :tournament, UOF.API.Sports.Tournament + embeds_one :season, UOF.API.Tournaments.Season + embeds_one :round, UOF.API.Tournaments.Round + embeds_one :season_coverage_info, UOF.API.Tournaments.SeasonCoverageInfo + embeds_one :coverage_info, UOF.API.Tournaments.CoverageInfo + embeds_many :groups, UOF.API.Tournaments.Group + end + + def changeset(model \\ %__MODULE__{}, params) do + params = params["tournament_info"] + params = bubble_up(params, "groups", "group") + + model + |> cast(params, []) + |> cast_embed(:tournament) + |> cast_embed(:season) + |> cast_embed(:season_coverage_info) + |> cast_embed(:coverage_info) + |> cast_embed(:round) + |> cast_embed(:groups) + end +end diff --git a/lib/uof/api/users.ex b/lib/uof/api/users.ex index f706e8a..be2f4e9 100644 --- a/lib/uof/api/users.ex +++ b/lib/uof/api/users.ex @@ -14,6 +14,7 @@ defmodule UOF.API.Users do case UOF.API.get("/users/whoami.xml") do {:ok, %_{status: 200, body: resp}} -> resp + |> UOF.API.Utils.xml_to_map() |> Map.get("bookmaker_details") |> changeset @@ -31,8 +32,6 @@ defmodule UOF.API.Users do end def changeset(model \\ %__MODULE__{}, params) do - params = sanitize(params) - model |> cast(params, [:expire_at, :bookmaker_id, :virtual_host]) |> apply diff --git a/lib/uof/api/utils.ex b/lib/uof/api/utils.ex new file mode 100644 index 0000000..0d82a68 --- /dev/null +++ b/lib/uof/api/utils.ex @@ -0,0 +1,51 @@ +# https://github.com/homanchou/elixir-xml-to-map +# https://github.com/qcam/saxy/issues/103 +defmodule UOF.API.Utils do + @moduledoc false + + def xml_to_map([{tag, attributes, content}]) do + xml_to_map({tag, attributes, content}) + end + + def xml_to_map([value]) do + to_string(value) |> String.trim() + end + + def xml_to_map({tag, [], content}) do + xml_to_mapd_content = xml_to_map(content) + %{to_string(tag) => xml_to_mapd_content} + end + + def xml_to_map({tag, attributes, content}) do + attributes_map = Map.new(attributes) + + xml_to_mapd_content = xml_to_map(content) + joined_content = Map.merge(xml_to_mapd_content, attributes_map) + + %{to_string(tag) => joined_content} + end + + def xml_to_map(list) when is_list(list) do + parsed_list = + list + |> Enum.reject(fn + str when is_binary(str) -> String.trim(str) == "" + _ -> false + end) + |> Enum.map(&{to_string(elem(&1, 0)), xml_to_map(&1)}) + + Enum.reduce(parsed_list, %{}, fn {k, v}, acc -> + case Map.get(acc, k) do + nil -> + for({key, value} <- v, into: %{}, do: {key, value}) + |> Map.merge(acc) + + [h | t] -> + Map.put(acc, k, [h | t] ++ [v[k]]) + + prev -> + Map.put(acc, k, [prev] ++ [v[k]]) + end + end) + end +end diff --git a/mix.exs b/mix.exs index 6a6b0f0..b555d2e 100644 --- a/mix.exs +++ b/mix.exs @@ -29,7 +29,7 @@ defmodule UofApi.MixProject do {:saxy, "~> 1.5"}, {:req, "~> 0.4.14"}, {:tesla, "~> 1.9"}, - {:tesla_middleware_xml, "~> 1.0.1"}, + {:tesla_middleware_xml, "~> 2.0.0"}, {:xml_builder, "~> 2.3"}, # dev {:ex_doc, "~> 0.31", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index 71243c3..f4f86d6 100644 --- a/mix.lock +++ b/mix.lock @@ -1,18 +1,18 @@ %{ - "castore": {:hex, :castore, "1.0.6", "ffc42f110ebfdafab0ea159cd43d31365fa0af0ce4a02ecebf1707ae619ee727", [:mix], [], "hexpm", "374c6e7ca752296be3d6780a6d5b922854ffcc74123da90f2f328996b962d33a"}, + "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "ecto": {:hex, :ecto, "3.11.2", "e1d26be989db350a633667c5cda9c3d115ae779b66da567c68c80cfb26a8c9ee", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3c38bca2c6f8d8023f2145326cc8a80100c3ffe4dcbd9842ff867f7fc6156c65"}, "ex_doc": {:hex, :ex_doc, "0.32.1", "21e40f939515373bcdc9cffe65f3b3543f05015ac6c3d01d991874129d173420", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5142c9db521f106d61ff33250f779807ed2a88620e472ac95dc7d59c380113da"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, - "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, + "hpax": {:hex, :hpax, "0.2.0", "5a58219adcb75977b2edce5eb22051de9362f08236220c9e859a47111c194ff5", [:mix], [], "hexpm", "bea06558cdae85bed075e6c036993d43cd54d447f76d8190a8db0dc5893fa2f1"}, "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.5", "e0ff5a7c708dda34311f7522a8758e23bfcd7d8d8068dc312b5eb41c6fd76eba", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "94d2e986428585a21516d7d7149781480013c56e30c6a233534bedf38867a59a"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, - "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, + "mint": {:hex, :mint, "1.6.0", "88a4f91cd690508a04ff1c3e28952f322528934be541844d54e0ceb765f01d5e", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "3c5ae85d90a5aca0a49c0d8b67360bbe407f3b54f1030a111047ff988e8fefaa"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, "nimble_ownership": {:hex, :nimble_ownership, "0.3.1", "99d5244672fafdfac89bfad3d3ab8f0d367603ce1dc4855f86a1c75008bce56f", [:mix], [], "hexpm", "4bf510adedff0449a1d6e200e43e57a814794c8b5b6439071274d248d272a549"}, @@ -24,7 +24,7 @@ "stream_split": {:hex, :stream_split, "0.1.7", "2d3fd1fd21697da7f91926768d65f79409086052c9ec7ae593987388f52425f8", [:mix], [], "hexpm", "1dc072ff507a64404a0ad7af90df97096183fee8eeac7b300320cea7c4679147"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, - "tesla_middleware_xml": {:hex, :tesla_middleware_xml, "1.0.1", "e819e510734d166b1155f72cda3f5ea006352779c3fb1206a9599bddc9e9cac0", [:make, :mix], [{:tesla, "~> 1.9", [hex: :tesla, repo: "hexpm", optional: false]}, {:xml_json, "~> 0.4.2", [hex: :xml_json, repo: "hexpm", optional: false]}], "hexpm", "c8bd2f253163c9e411ab617489b7225977eb189717fd6534026534e99b9fa5c9"}, + "tesla_middleware_xml": {:hex, :tesla_middleware_xml, "2.0.0", "79e457ce4799d78256f3ae56d397a7eacd20b3ef10bc564a3511493ff5db6553", [:make, :mix], [{:saxy, "~> 1.5", [hex: :saxy, repo: "hexpm", optional: true]}, {:tesla, "~> 1.9", [hex: :tesla, repo: "hexpm", optional: false]}, {:xml_json, "~> 0.4.2", [hex: :xml_json, repo: "hexpm", optional: true]}], "hexpm", "7eb184321cf1128b05ebfca444bc31ad58e15db2c1953b23c6cd527ee06d84e1"}, "xml_builder": {:hex, :xml_builder, "2.3.0", "69d214c6ad41ae1300b36acff4367551cdfd9dc1b860affc16e103c6b1589053", [:mix], [], "hexpm", "972ec33346a225cd5acd14ab23d4e79042bd37cb904e07e24cd06992dde1a0ed"}, "xml_json": {:hex, :xml_json, "0.4.2", "dfb8f34e5dc82b2642ea71bc92b853098f8d110367d40e26c634244bc2ebe04c", [:mix], [{:saxy, "~> 1.2", [hex: :saxy, repo: "hexpm", optional: false]}], "hexpm", "9102dfa0b2878a8808262d1602185543735acd933c978fb4c4a1b065c5f0ec79"}, } diff --git a/test/uof/api/sports/tournament_test.exs b/test/uof/api/sports/tournament_test.exs new file mode 100644 index 0000000..150dec5 --- /dev/null +++ b/test/uof/api/sports/tournament_test.exs @@ -0,0 +1,102 @@ +defmodule UOF.API.Sports.Tournament.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get, url: "/sports/en/tournaments.xml"} -> + resp = File.read!("test/data/tournaments.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + + %{method: :get, url: "/sports/en/tournaments/sr:tournament:7/info.xml"} -> + resp = File.read!("test/data/tournament_info.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Sports.Tournament.all/{0, 1} response" do + tournaments = UOF.API.Sports.Tournament.all() + + tournament = Enum.at(tournaments, 2) + + # tournament attributes + assert Enum.count(tournaments) == 1312 + assert tournament.id == "sr:tournament:7" + assert tournament.name == "UEFA Champions League" + # tournament -> sport + sport = tournament.sport + assert sport.id == "sr:sport:1" + assert sport.name == "Soccer" + # tournament -> category + category = tournament.category + assert category.id == "sr:category:393" + assert category.name == "International Clubs" + # tournament -> current season + current_season = tournament.current_season + assert current_season.start_date == "2023-06-27" + assert current_season.end_date == "2024-06-01" + assert current_season.year == "23/24" + assert current_season.id == "sr:season:106479" + # tournament -> season coverage + season_coverage = tournament.season_coverage_info + assert season_coverage.season_id == "sr:season:106479" + assert season_coverage.scheduled == 215 + assert season_coverage.played == 210 + assert season_coverage.max_covered == 136 + assert season_coverage.max_coverage_level == "gold" + assert season_coverage.min_coverage_level == "silver" + end + + test "can parse UOF.API.Sports.Tournament.show/{1, 2} response" do + {:ok, info} = UOF.API.Sports.Tournament.show("sr:tournament:7") + + # tournament + assert info.tournament.id == "sr:tournament:7" + assert info.tournament.name == "UEFA Champions League" + # tournament -> sport + assert info.tournament.sport.id == "sr:sport:1" + assert info.tournament.sport.name == "Soccer" + # tournament -> category + assert info.tournament.category.id == "sr:category:393" + assert info.tournament.category.name == "International Clubs" + # tournament -> current season + assert info.tournament.current_season.start_date == "2023-06-27" + assert info.tournament.current_season.end_date == "2024-06-01" + assert info.tournament.current_season.year == "23/24" + assert info.tournament.current_season.id == "sr:season:106479" + # season + assert info.season.start_date == "2023-06-27" + assert info.season.end_date == "2024-06-01" + assert info.season.year == "23/24" + assert info.season.id == "sr:season:106479" + assert info.season.name == "UEFA Champions League 23/24" + # round + assert info.round.type == "cup" + assert info.round.name == "Quarterfinal" + assert info.round.cup_round_matches == 2 + assert info.round.cup_round_match_number == 2 + # coverage info + assert info.coverage_info.live_coverage == true + # groups + group = hd(info.groups) + assert Enum.count(info.groups) == 9 + assert group.name == "A" + assert group.id == "sr:group:74525" + # group -> competitors + # * competitors + assert Enum.count(group.competitors) == 4 + competitor = hd(group.competitors) + assert competitor.id == "sr:competitor:2672" + assert competitor.name == "Bayern Munich" + assert competitor.abbreviation == "BMU" + assert competitor.country == "Germany" + assert competitor.country_code == "DEU" + assert competitor.gender == "male" + [reference] = competitor.references + assert reference.name == "betradar" + assert reference.value == "6631" + end +end diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index cea582c..8a29fe1 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -35,14 +35,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/timeline.xml")} end - defp fetch_mock_data(["sports", _lang, "tournaments.xml"]) do - {:ok, File.read!("test/data/tournaments.xml")} - end - - defp fetch_mock_data(["sports", _lang, "tournaments", _tournament, "info.xml"]) do - {:ok, File.read!("test/data/tournament_info.xml")} - end - defp fetch_mock_data(["sports", _lang, "players", _player, "profile.xml"]) do {:ok, File.read!("test/data/player_profile.xml")} end @@ -414,88 +406,6 @@ defmodule UOF.API.Sports.Test do assert match_ended_event.match_clock == "60:00" end - test "can parse UOF.API.Sports.tournaments/{0, 1} response" do - {:ok, data} = UOF.API.Sports.tournaments() - - tournament = Enum.at(data.tournaments, 2) - - # tournament attributes - assert Enum.count(data.tournaments) == 1312 - assert tournament.id == "sr:tournament:7" - assert tournament.name == "UEFA Champions League" - # tournament -> sport - sport = tournament.sport - assert sport.id == "sr:sport:1" - assert sport.name == "Soccer" - # tournament -> category - category = tournament.category - assert category.id == "sr:category:393" - assert category.name == "International Clubs" - # tournament -> current season - current_season = tournament.current_season - assert current_season.start_date == "2023-06-27" - assert current_season.end_date == "2024-06-01" - assert current_season.year == "23/24" - assert current_season.id == "sr:season:106479" - # tournament -> season coverage - season_coverage = tournament.season_coverage - assert season_coverage.season_id == "sr:season:106479" - assert season_coverage.scheduled == 215 - assert season_coverage.played == 210 - assert season_coverage.max_covered == 136 - assert season_coverage.max_coverage_level == "gold" - assert season_coverage.min_coverage_level == "silver" - end - - test "can parse UOF.API.Sports.tournament/{1, 2} response" do - {:ok, info} = UOF.API.Sports.tournament("sr:tournament:7") - - # tournament - assert info.tournament.id == "sr:tournament:7" - assert info.tournament.name == "UEFA Champions League" - # tournament -> sport - assert info.tournament.sport.id == "sr:sport:1" - assert info.tournament.sport.name == "Soccer" - # tournament -> category - assert info.tournament.category.id == "sr:category:393" - assert info.tournament.category.name == "International Clubs" - # tournament -> current season - assert info.tournament.current_season.start_date == "2023-06-27" - assert info.tournament.current_season.end_date == "2024-06-01" - assert info.tournament.current_season.year == "23/24" - assert info.tournament.current_season.id == "sr:season:106479" - # season - assert info.season.start_date == "2023-06-27" - assert info.season.end_date == "2024-06-01" - assert info.season.year == "23/24" - assert info.season.id == "sr:season:106479" - assert info.season.name == "UEFA Champions League 23/24" - # round - assert info.round.type == "cup" - assert info.round.name == "Quarterfinal" - assert info.round.cup_round_matches == 2 - assert info.round.cup_round_match_number == 2 - # coverage info - assert info.coverage_info.live_coverage == true - # groups - group = hd(info.groups) - assert Enum.count(info.groups) == 9 - assert group.name == "A" - assert group.id == "sr:group:74525" - # group -> competitors - assert Enum.count(group.competitors) == 4 - competitor = hd(group.competitors) - assert competitor.id == "sr:competitor:2672" - assert competitor.name == "Bayern Munich" - assert competitor.abbreviation == "BMU" - assert competitor.country == "Germany" - assert competitor.country_code == "DEU" - assert competitor.gender == "male" - [reference] = competitor.references - assert reference.name == "betradar" - assert reference.value == "6631" - end - test "can parse 'sports/:lang/tournaments/:tournament/seasons.xml' response" do # TO-DO end From a48d4600b2a192a24dc74caf63094311ad72408c Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 12:57:26 +0200 Subject: [PATCH 23/34] players --- lib/uof/api/players.ex | 20 ++++++++++++++++++ lib/uof/api/players/player.ex | 38 +++++++++++++++++++++++++++++++++++ lib/uof/api/sports.ex | 19 ------------------ test/uof/api/sports_test.exs | 16 --------------- 4 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 lib/uof/api/players.ex create mode 100644 lib/uof/api/players/player.ex diff --git a/lib/uof/api/players.ex b/lib/uof/api/players.ex new file mode 100644 index 0000000..e4cd7b3 --- /dev/null +++ b/lib/uof/api/players.ex @@ -0,0 +1,20 @@ +defmodule UOF.API.Players do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + def show(id, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/players/#{id}/profile.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("player_profile") + |> Map.get("player") + |> UOF.API.Players.Player.changeset() + |> apply + + {:error, _} = error -> + error + end + end +end diff --git a/lib/uof/api/players/player.ex b/lib/uof/api/players/player.ex new file mode 100644 index 0000000..7c8ef79 --- /dev/null +++ b/lib/uof/api/players/player.ex @@ -0,0 +1,38 @@ +defmodule UOF.API.Players.Player do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :type + field :date_of_birth + field :nationality + field :country_code + field :height, :integer + field :weight, :integer + field :jersey_number, :integer + field :full_name + field :gender + field :id + field :name + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :type, + :date_of_birth, + :nationality, + :country_code, + :height, + :weight, + :jersey_number, + :full_name, + :gender, + :id, + :name + ]) + end +end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index 8cb7848..adc6bfd 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -58,15 +58,6 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Schedule{}, endpoint) end - @doc """ - Get a list of all live fixtures. - """ - def live_schedule(lang \\ "en") do - endpoint = ["sports", lang, "schedules", "live", "schedule.xml"] - - HTTP.get(%UOF.API.Mappings.Schedule{}, endpoint) - end - @doc """ Get a lists of almost all fixtures Betradar offers prematch odds for. """ @@ -132,16 +123,6 @@ defmodule UOF.API.Sports do ## Entity Description ## ========================================================================= - @doc """ - Get the details of the given player. - """ - def player(player, lang \\ "en") do - # https://docs.betradar.com/display/BD/UOF+-+Player+profile - endpoint = ["sports", lang, "players", player, "profile.xml"] - - HTTP.get(%UOF.API.Mappings.PlayerProfile{}, endpoint) - end - @doc """ Get the details of the given competitor. """ diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index 8a29fe1..0cf5539 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -35,10 +35,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/timeline.xml")} end - defp fetch_mock_data(["sports", _lang, "players", _player, "profile.xml"]) do - {:ok, File.read!("test/data/player_profile.xml")} - end - defp fetch_mock_data(["sports", _lang, "competitors", _competitor, "profile.xml"]) do {:ok, File.read!("test/data/competitor_profile.xml")} end @@ -412,18 +408,6 @@ defmodule UOF.API.Sports.Test do ## Entity descriptions ## ========================================================================= - test "can parse UOF.API.Sports.player/{1, 2} response" do - {:ok, profile} = UOF.API.Sports.player("sr:player:72771") - - assert profile.player.date_of_birth == "1973-08-29" - assert profile.player.nationality == "Germany" - assert profile.player.country_code == "DEU" - assert profile.player.full_name == "Thomas Tuchel" - assert profile.player.gender == "male" - assert profile.player.id == "sr:player:72771" - assert profile.player.name == "Tuchel, Thomas" - end - test "can parse UOF.API.Sports.competitor/{1, 2} response" do {:ok, profile} = UOF.API.Sports.competitor("sr:competitor:2672") From d58e0a2f54b64307754680154e4d039f9cd3463a Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 12:57:33 +0200 Subject: [PATCH 24/34] schedules --- README.md | 5 ++- lib/uof/api/competitors.ex | 2 ++ lib/uof/api/fixtures.ex | 50 +++++++++++++++++++++++++++++ lib/uof/api/fixtures/venue.ex | 32 +++++++++++++++++++ lib/uof/api/schedules.ex | 38 ++++++++++++++++++++++ lib/uof/api/schedules/fixture.ex | 54 ++++++++++++++++++++++++++++++++ lib/uof/api/venues.ex | 2 ++ test/uof/api/players_test.exs | 27 ++++++++++++++++ 8 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 lib/uof/api/competitors.ex create mode 100644 lib/uof/api/fixtures.ex create mode 100644 lib/uof/api/fixtures/venue.ex create mode 100644 lib/uof/api/schedules.ex create mode 100644 lib/uof/api/schedules/fixture.ex create mode 100644 lib/uof/api/venues.ex create mode 100644 test/uof/api/players_test.exs diff --git a/README.md b/README.md index efad008..465ed26 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ Elixir client for Betradar's Unified Odds Feed (UOF) HTTP API. This project implements Betradar's Custom Bet, Probability, Recovery and Sports -APIs, which can be found in the `UOF.API.{CustomBet, Probability, Recovery, -Sports}` modules, respectively. +APIs. ## Get Started @@ -36,7 +35,7 @@ to use. Fetch all available fixtures. ```elixir -fixtures = UOF.API.Sports.fixtures +{:ok, fixtures} = UOF.API.Fixtures.all() ``` Given a list fixtures, count how many of them there are. diff --git a/lib/uof/api/competitors.ex b/lib/uof/api/competitors.ex new file mode 100644 index 0000000..85acee9 --- /dev/null +++ b/lib/uof/api/competitors.ex @@ -0,0 +1,2 @@ +defmodule UOF.API.Competitors do +end diff --git a/lib/uof/api/fixtures.ex b/lib/uof/api/fixtures.ex new file mode 100644 index 0000000..15d17c9 --- /dev/null +++ b/lib/uof/api/fixtures.ex @@ -0,0 +1,50 @@ +defmodule UOF.API.Fixtures do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @doc """ + List all the available fixtures. + """ + def all(lang \\ "en", filter \\ & &1, map \\ & &1) do + all(lang, 0, 1000, filter, map, []) + end + + # def changed() do + # end + + # def changed_results() do + # end + + defp all(lang, start, limit, filter, map, acc) do + case pre_schedule(lang, start, limit) do + [] -> + acc + + events -> + # TO-DO: Return subset of fields (eg. only fixture ids) + # events = maybe_filter_fixtures(events, filter) + events = for e <- events, filter.(e), do: map.(e) + all(lang, start + limit, limit, filter, map, events ++ acc) + end + end + + defp pre_schedule(lang \\ "en", start \\ 0, limit \\ 100) do + # TO-DO: staged tournaments + # https://docs.betradar.com/display/BD/UOF+-+Formula+1 + case UOF.API.get("/sports/#{lang}/schedules/pre/schedule.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("schedule") + |> Map.get("sport_event") + |> Enum.map(fn x -> + {:ok, x} = apply(UOF.API.Schedules.Fixture.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end +end diff --git a/lib/uof/api/fixtures/venue.ex b/lib/uof/api/fixtures/venue.ex new file mode 100644 index 0000000..5e68c3c --- /dev/null +++ b/lib/uof/api/fixtures/venue.ex @@ -0,0 +1,32 @@ +defmodule UOF.API.Fixtures.Venue do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + embedded_schema do + field :id, :string + field :name, :string + field :capacity, :integer + field :city_name, :string + field :country_name, :string + field :map_coordinates, :string + field :country_code, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :id, + :name, + :capacity, + :city_name, + :country_name, + :country_code, + :map_coordinates + ]) + end +end diff --git a/lib/uof/api/schedules.ex b/lib/uof/api/schedules.ex new file mode 100644 index 0000000..e13464c --- /dev/null +++ b/lib/uof/api/schedules.ex @@ -0,0 +1,38 @@ +defmodule UOF.API.Schedules do + import UOF.API.EctoHelpers + alias UOF.API.Schedules.Fixture + + def today(lang \\ "en") do + case UOF.API.get("/sports/#{lang}/schedules/live/schedule.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("schedule") + |> Map.get("sport_event") + |> Enum.map(fn x -> + {:ok, x} = apply(Fixture.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end + + def at(date, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/schedules/#{date}/schedule.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("schedule") + |> Map.get("sport_event") + |> Enum.map(fn x -> + {:ok, x} = apply(Fixture.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end +end diff --git a/lib/uof/api/schedules/fixture.ex b/lib/uof/api/schedules/fixture.ex new file mode 100644 index 0000000..17298ca --- /dev/null +++ b/lib/uof/api/schedules/fixture.ex @@ -0,0 +1,54 @@ +defmodule UOF.API.Schedules.Fixture do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @primary_key false + + embedded_schema do + field :id, :string + field :status, :string + field :next_live_time, :string + field :scheduled, :string + field :start_time_tbd, :boolean + + embeds_one :venue, UOF.API.Fixtures.Venue + embeds_one :season, UOF.API.Tournaments.Season + embeds_one :round, UOF.API.Tournaments.Round + embeds_one :tournament, UOF.API.Sports.Tournament + # embeds_many :competitors, UOF.API.Fixtures.Competitor + end + + # tournament round + # attribute(:betradar_id, cast: :integer) + # attribute(:betradar_name) + # attribute(:type) + # # cup + # attribute(:name) + # attribute(:cup_round_matches, cast: :integer) + # attribute(:cup_round_match_number, cast: :integer) + # attribute(:other_match_id) + # # group + # attribute(:number) + # attribute(:group) + # attribute(:group_id) + # attribute(:group_long_name) + # # qualification + # # ??? + # attribute(:phase) + + def changeset(model \\ %__MODULE__{}, params) do + params = rename(params, "tournament_round", "round", nil) + + model + |> cast(params, [:id, :status, :next_live_time, :scheduled, :start_time_tbd]) + |> cast_embed(:venue) + |> cast_embed(:season) + |> cast_embed(:round) + |> cast_embed(:tournament) + + # |> cast_embed(:competitors) + end +end diff --git a/lib/uof/api/venues.ex b/lib/uof/api/venues.ex new file mode 100644 index 0000000..79ebbcb --- /dev/null +++ b/lib/uof/api/venues.ex @@ -0,0 +1,2 @@ +defmodule UOF.API.Venues do +end diff --git a/test/uof/api/players_test.exs b/test/uof/api/players_test.exs new file mode 100644 index 0000000..3292c65 --- /dev/null +++ b/test/uof/api/players_test.exs @@ -0,0 +1,27 @@ +defmodule UOF.API.Players.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/player_profile.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Sports.player/{1, 2} response" do + {:ok, player} = UOF.API.Players.show("sr:player:72771") + + assert player.date_of_birth == "1973-08-29" + assert player.nationality == "Germany" + assert player.country_code == "DEU" + assert player.full_name == "Thomas Tuchel" + assert player.gender == "male" + assert player.id == "sr:player:72771" + assert player.name == "Tuchel, Thomas" + end +end From c54044bec0114dd9363485d8c440524ae3cf9a7d Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 13:17:31 +0200 Subject: [PATCH 25/34] venues --- lib/uof/api/sports.ex | 10 ---------- lib/uof/api/venues.ex | 18 +++++++++++++++++ lib/uof/api/venues/venue.ex | 30 ++++++++++++++++++++++++++++ test/uof/api/players_test.exs | 2 +- test/uof/api/sports_test.exs | 26 ------------------------ test/uof/api/venues_test.exs | 37 +++++++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 37 deletions(-) create mode 100644 lib/uof/api/venues/venue.ex create mode 100644 test/uof/api/venues_test.exs diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index adc6bfd..e43dda8 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -132,14 +132,4 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.CompetitorProfile{}, endpoint) end - - @doc """ - Get the details of the given venue. - """ - def venue(venue, lang \\ "en") do - # https://docs.betradar.com/display/BD/UOF+-+Venues - endpoint = ["sports", lang, "venues", venue, "profile.xml"] - - HTTP.get(%UOF.API.Mappings.VenueProfile{}, endpoint) - end end diff --git a/lib/uof/api/venues.ex b/lib/uof/api/venues.ex index 79ebbcb..b7a49f1 100644 --- a/lib/uof/api/venues.ex +++ b/lib/uof/api/venues.ex @@ -1,2 +1,20 @@ defmodule UOF.API.Venues do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + def show(id, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/venues/#{id}/profile.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("venue_summary") + |> Map.get("venue") + |> UOF.API.Venues.Venue.changeset() + |> apply + + {:error, _} = error -> + error + end + end end diff --git a/lib/uof/api/venues/venue.ex b/lib/uof/api/venues/venue.ex new file mode 100644 index 0000000..b4c6d3c --- /dev/null +++ b/lib/uof/api/venues/venue.ex @@ -0,0 +1,30 @@ +defmodule UOF.API.Venues.Venue do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :id + field :name + field :capacity, :integer + field :city_name + field :country_name + field :map_coordinates + field :country_code + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :id, + :name, + :capacity, + :city_name, + :country_name, + :map_coordinates, + :country_code + ]) + end +end diff --git a/test/uof/api/players_test.exs b/test/uof/api/players_test.exs index 3292c65..6b51021 100644 --- a/test/uof/api/players_test.exs +++ b/test/uof/api/players_test.exs @@ -13,7 +13,7 @@ defmodule UOF.API.Players.Test do :ok end - test "can parse UOF.API.Sports.player/{1, 2} response" do + test "can parse UOF.API.Players.show/{1, 2} response" do {:ok, player} = UOF.API.Players.show("sr:player:72771") assert player.date_of_birth == "1973-08-29" diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index 0cf5539..b975351 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -39,10 +39,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/competitor_profile.xml")} end - defp fetch_mock_data(["sports", _lang, "venues", _venue, "profile.xml"]) do - {:ok, File.read!("test/data/venue_profile.xml")} - end - defp fetch_mock_data(_endpoint) do {:error, "mock data not found"} end @@ -465,26 +461,4 @@ defmodule UOF.API.Sports.Test do assert player.id == "sr:player:8959" assert player.name == "Neuer, Manuel" end - - test "can parse UOF.API.Sports.venue/{1, 2} response" do - {:ok, profile} = UOF.API.Sports.venue("sr:venue:574") - - # venue - assert profile.venue.id == "sr:venue:574" - assert profile.venue.name == "Allianz Arena" - assert profile.venue.capacity == 75000 - assert profile.venue.city_name == "Munich" - assert profile.venue.country_name == "Germany" - assert profile.venue.country_code == "DEU" - assert profile.venue.map_coordinates == "48.218777,11.624748" - # home teams - home_team = hd(profile.home_teams) - assert Enum.count(profile.home_teams) == 1 - assert home_team.id == "sr:competitor:2672" - assert home_team.name == "Bayern Munich" - assert home_team.abbreviation == "BMU" - assert home_team.country == "Germany" - assert home_team.country_code == "DEU" - assert home_team.gender == "male" - end end diff --git a/test/uof/api/venues_test.exs b/test/uof/api/venues_test.exs new file mode 100644 index 0000000..a6c0bff --- /dev/null +++ b/test/uof/api/venues_test.exs @@ -0,0 +1,37 @@ +defmodule UOF.API.Venues.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/venue_profile.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Sports.venue/{1, 2} response" do + {:ok, venue} = UOF.API.Venues.show("sr:venue:574") + + # venue + assert venue.id == "sr:venue:574" + assert venue.name == "Allianz Arena" + assert venue.capacity == 75000 + assert venue.city_name == "Munich" + assert venue.country_name == "Germany" + assert venue.country_code == "DEU" + assert venue.map_coordinates == "48.218777,11.624748" + # # home teams + # home_team = hd(profile.home_teams) + # assert Enum.count(profile.home_teams) == 1 + # assert home_team.id == "sr:competitor:2672" + # assert home_team.name == "Bayern Munich" + # assert home_team.abbreviation == "BMU" + # assert home_team.country == "Germany" + # assert home_team.country_code == "DEU" + # assert home_team.gender == "male" + end +end From f17d8bdb627c8ab5963d7f89e4db6064e9783b1e Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 19:35:49 +0200 Subject: [PATCH 26/34] competitors --- lib/uof/api/competitors.ex | 19 +++++ lib/uof/api/competitors/competitor.ex | 41 +++++++++++ lib/uof/api/competitors/competitor_profile.ex | 25 +++++++ lib/uof/api/competitors/jersey.ex | 34 +++++++++ lib/uof/api/competitors/manager.ex | 24 ++++++ lib/uof/api/sports.ex | 61 +++++++++++++--- lib/uof/api/sports/category.ex | 61 ++-------------- lib/uof/api/sports/sport.ex | 21 ------ lib/uof/api/sports/tournament.ex | 2 +- test/uof/api/competitors_test.exs | 73 +++++++++++++++++++ test/uof/api/sports/category_test.exs | 10 +-- test/uof/api/sports/sport_test.exs | 2 +- test/uof/api/sports_test.exs | 64 ---------------- 13 files changed, 274 insertions(+), 163 deletions(-) create mode 100644 lib/uof/api/competitors/competitor.ex create mode 100644 lib/uof/api/competitors/competitor_profile.ex create mode 100644 lib/uof/api/competitors/jersey.ex create mode 100644 lib/uof/api/competitors/manager.ex create mode 100644 test/uof/api/competitors_test.exs diff --git a/lib/uof/api/competitors.ex b/lib/uof/api/competitors.ex index 85acee9..313b29a 100644 --- a/lib/uof/api/competitors.ex +++ b/lib/uof/api/competitors.ex @@ -1,2 +1,21 @@ defmodule UOF.API.Competitors do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + def show(id, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/competitors/#{id}/profile.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("competitor_profile") + |> bubble_up("jerseys", "jersey") + |> bubble_up("players", "player") + |> UOF.API.Competitors.CompetitorProfile.changeset() + |> apply + + {:error, _} = error -> + error + end + end end diff --git a/lib/uof/api/competitors/competitor.ex b/lib/uof/api/competitors/competitor.ex new file mode 100644 index 0000000..714f6dc --- /dev/null +++ b/lib/uof/api/competitors/competitor.ex @@ -0,0 +1,41 @@ +defmodule UOF.API.Competitors.Competitor do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :id + field :name + field :state + field :country + field :country_code + field :abbreviation + field :qualifier + field :virtual + field :gender + field :short_name + embeds_one :sport, UOF.API.Sports.Sport + embeds_one :category, UOF.API.Sports.Category + # embeds_many, :reference_id + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :id, + :name, + :state, + :country, + :country_code, + :abbreviation, + :qualifier, + :virtual, + :gender, + :short_name + ]) + |> cast_embed(:sport) + |> cast_embed(:category) + end +end diff --git a/lib/uof/api/competitors/competitor_profile.ex b/lib/uof/api/competitors/competitor_profile.ex new file mode 100644 index 0000000..e1b2375 --- /dev/null +++ b/lib/uof/api/competitors/competitor_profile.ex @@ -0,0 +1,25 @@ +defmodule UOF.API.Competitors.CompetitorProfile do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + embeds_one :competitor, UOF.API.Competitors.Competitor + embeds_one :manager, UOF.API.Competitors.Manager + embeds_one :venue, UOF.API.Venues.Venue + embeds_many :jerseys, UOF.API.Competitors.Jersey + embeds_many :players, UOF.API.Players.Player + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, []) + |> cast_embed(:competitor) + |> cast_embed(:manager) + |> cast_embed(:venue) + |> cast_embed(:jerseys) + |> cast_embed(:players) + end +end diff --git a/lib/uof/api/competitors/jersey.ex b/lib/uof/api/competitors/jersey.ex new file mode 100644 index 0000000..a48cda9 --- /dev/null +++ b/lib/uof/api/competitors/jersey.ex @@ -0,0 +1,34 @@ +defmodule UOF.API.Competitors.Jersey do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :type + field :base + field :sleeve + field :number + field :stripes, :boolean + field :horizontal_stripes, :boolean + field :squares, :boolean + field :split, :boolean + field :shirt_type + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :type, + :base, + :sleeve, + :number, + :stripes, + :horizontal_stripes, + :squares, + :split, + :shirt_type + ]) + end +end diff --git a/lib/uof/api/competitors/manager.ex b/lib/uof/api/competitors/manager.ex new file mode 100644 index 0000000..0e006b6 --- /dev/null +++ b/lib/uof/api/competitors/manager.ex @@ -0,0 +1,24 @@ +defmodule UOF.API.Competitors.Manager do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :id + field :name + field :country_code + field :nationality + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :id, + :name, + :country_code, + :nationality + ]) + end +end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index e43dda8..610206b 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -1,6 +1,55 @@ defmodule UOF.API.Sports do + @moduledoc """ + """ + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + alias UOF.API.Utils.HTTP + @doc """ + List all the available sports. + """ + # @spec all(lang :: String.t()) :: list(__MODULE__.t()) + def all(lang \\ "en") do + case UOF.API.get("/sports/#{lang}/sports.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("sports") + |> Map.get("sport") + |> Enum.map(fn x -> + {:ok, x} = apply(UOF.API.Sports.Sport.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end + + @doc """ + List all the available categories for the given sport. + """ + # @spec categories(sport :: String.t(), lang :: String.t()) :: list(__MODULE__.t()) + def categories(sport, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/sports/#{sport}/categories.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("sport_categories") + |> Map.get("categories") + |> Map.get("category") + |> Enum.map(fn x -> + {:ok, x} = apply(UOF.API.Sports.Category.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end + ## Auxiliary functions ## ========================================================================= def fixtures(filter \\ & &1, map \\ & &1) do @@ -120,16 +169,4 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Timeline{}, endpoint) end - - ## Entity Description - ## ========================================================================= - @doc """ - Get the details of the given competitor. - """ - def competitor(competitor, lang \\ "en") do - # https://docs.betradar.com/display/BD/UOF+-+Competitors+profile - endpoint = ["sports", lang, "competitors", competitor, "profile.xml"] - - HTTP.get(%UOF.API.Mappings.CompetitorProfile{}, endpoint) - end end diff --git a/lib/uof/api/sports/category.ex b/lib/uof/api/sports/category.ex index bba57e9..819d364 100644 --- a/lib/uof/api/sports/category.ex +++ b/lib/uof/api/sports/category.ex @@ -9,73 +9,22 @@ defmodule UOF.API.Sports.Category do import Ecto.Changeset import UOF.API.EctoHelpers - @doc """ - List all the available categories for the given sport. - """ - @spec by_sport(sport :: String.t(), lang :: String.t()) :: list(__MODULE__.t()) - def by_sport(sport, lang \\ "en") do - case UOF.API.get("/sports/#{lang}/sports/#{sport}/categories.xml") do - {:ok, %_{status: 200, body: resp}} -> - resp - |> UOF.API.Utils.xml_to_map() - |> Map.get("sport_categories") - |> changeset - |> apply - - {:error, _} = error -> - error - end - end - - @type sport :: %UOF.API.Sports.Category.Sport{ - id: String.t(), - name: String.t() - } - - @type category :: %UOF.API.Sports.Category.Category{ + @type t :: %UOF.API.Sports.Category{ id: String.t(), name: String.t(), country_code: String.t() } - @type t :: %__MODULE__{ - sport: sport(), - categories: list(category()) - } - @primary_key false embedded_schema do - embeds_one :sport, Sport, primary_key: false do - field :id, :string - field :name, :string - end - - embeds_many :categories, Category, primary_key: false do - field :id, :string - field :name, :string - field :country_code, :string - end + field :id, :string + field :name, :string + field :country_code, :string end @doc false - def changeset(model \\ %__MODULE__{}, params) - - def changeset(%__MODULE__{} = model, params) do - params = bubble_up(params, "categories", "category") - - model - |> cast(params, []) - |> cast_embed(:sport, with: &changeset/2) - |> cast_embed(:categories, with: &changeset/2) - end - - def changeset(%UOF.API.Sports.Category.Sport{} = model, params) do - model - |> cast(params, [:id, :name]) - end - - def changeset(%UOF.API.Sports.Category.Category{} = model, params) do + def changeset(model \\ %__MODULE__{}, params) do model |> cast(params, [:id, :name, :country_code]) end diff --git a/lib/uof/api/sports/sport.ex b/lib/uof/api/sports/sport.ex index 530e21d..1dfdf7b 100644 --- a/lib/uof/api/sports/sport.ex +++ b/lib/uof/api/sports/sport.ex @@ -5,27 +5,6 @@ defmodule UOF.API.Sports.Sport do import Ecto.Changeset import UOF.API.EctoHelpers - @doc """ - List all the available sports. - """ - @spec all(lang :: String.t()) :: list(__MODULE__.t()) - def all(lang \\ "en") do - case UOF.API.get("/sports/#{lang}/sports.xml") do - {:ok, %_{status: 200, body: resp}} -> - resp - |> UOF.API.Utils.xml_to_map() - |> Map.get("sports") - |> Map.get("sport") - |> Enum.map(fn x -> - {:ok, x} = apply(changeset(x)) - x - end) - - {:error, _} = error -> - error - end - end - @type t :: %__MODULE__{ id: String.t(), name: String.t() diff --git a/lib/uof/api/sports/tournament.ex b/lib/uof/api/sports/tournament.ex index a327bf9..0c1cc3d 100644 --- a/lib/uof/api/sports/tournament.ex +++ b/lib/uof/api/sports/tournament.ex @@ -95,7 +95,7 @@ defmodule UOF.API.Sports.Tournament do end embeds_one :sport, UOF.API.Sports.Sport - embeds_one :category, UOF.API.Sports.Category.Category + embeds_one :category, UOF.API.Sports.Category end def from_simple_form({element, attrs, elements}) do diff --git a/test/uof/api/competitors_test.exs b/test/uof/api/competitors_test.exs new file mode 100644 index 0000000..1447753 --- /dev/null +++ b/test/uof/api/competitors_test.exs @@ -0,0 +1,73 @@ +defmodule UOF.API.Competitors.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/competitor_profile.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Competitors.show/{1, 2} response" do + {:ok, profile} = UOF.API.Competitors.show("sr:competitor:2672") + + # competitor + assert profile.competitor.id == "sr:competitor:2672" + assert profile.competitor.name == "Bayern Munich" + assert profile.competitor.abbreviation == "BMU" + assert profile.competitor.country == "Germany" + assert profile.competitor.country_code == "DEU" + assert profile.competitor.gender == "male" + # competitor -> sport + assert profile.competitor.sport.id == "sr:sport:1" + assert profile.competitor.sport.name == "Soccer" + # competitor -> category + assert profile.competitor.category.id == "sr:category:30" + assert profile.competitor.category.name == "Germany" + assert profile.competitor.category.country_code == "DEU" + # venue + assert profile.venue.id == "sr:venue:574" + assert profile.venue.name == "Allianz Arena" + assert profile.venue.capacity == 75000 + assert profile.venue.city_name == "Munich" + assert profile.venue.country_name == "Germany" + assert profile.venue.country_code == "DEU" + assert profile.venue.map_coordinates == "48.218777,11.624748" + # jerseys + jersey = hd(profile.jerseys) + assert Enum.count(profile.jerseys) == 4 + assert jersey.type == "home" + assert jersey.base == "fdfcfc" + assert jersey.sleeve == "ff0000" + assert jersey.number == "f90606" + assert jersey.stripes == false + assert jersey.horizontal_stripes == false + assert jersey.squares == false + assert jersey.split == false + assert jersey.shirt_type == "short_sleeves" + # manager + assert profile.manager.id == "sr:player:72771" + assert profile.manager.name == "Tuchel, Thomas" + assert profile.manager.nationality == "Germany" + assert profile.manager.country_code == "DEU" + # players + player = hd(profile.players) + assert Enum.count(profile.players) == 32 + assert player.type == "goalkeeper" + assert player.date_of_birth == "1986-03-27" + assert player.nationality == "Germany" + assert player.country_code == "DEU" + assert player.height == 193 + assert player.weight == 93 + assert player.jersey_number == 1 + assert player.full_name == "Manuel Peter Neuer" + assert player.gender == "male" + assert player.id == "sr:player:8959" + assert player.name == "Neuer, Manuel" + end +end diff --git a/test/uof/api/sports/category_test.exs b/test/uof/api/sports/category_test.exs index 9b9a08e..1acc9ca 100644 --- a/test/uof/api/sports/category_test.exs +++ b/test/uof/api/sports/category_test.exs @@ -13,15 +13,9 @@ defmodule UOF.API.Sports.Category.Test do :ok end - test "can parse UOF.API.Sports.Category.by_sport/{1, 2} response" do - {:ok, sport_categories} = UOF.API.Sports.Category.by_sport("sr:sport:1") + test "can parse UOF.API.Sports.categories/{1, 2} response" do + categories = UOF.API.Sports.categories("sr:sport:1") - # sport - sport = sport_categories.sport - assert sport.id == "sr:sport:1" - assert sport.name == "Soccer" - # categories - categories = sport_categories.categories category = hd(categories) assert Enum.count(categories) == 224 assert category.id == "sr:category:1" diff --git a/test/uof/api/sports/sport_test.exs b/test/uof/api/sports/sport_test.exs index 17d15c9..9164f9d 100644 --- a/test/uof/api/sports/sport_test.exs +++ b/test/uof/api/sports/sport_test.exs @@ -14,7 +14,7 @@ defmodule UOF.API.Sports.Sport.Test do end test "can parse UOF.API.Sports.Sport.all/{0, 1} response" do - sports = UOF.API.Sports.Sport.all() + sports = UOF.API.Sports.all() assert Enum.count(sports) == 204 assert hd(sports).id == "sr:sport:143" diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index b975351..be0d610 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -35,10 +35,6 @@ defmodule UOF.API.Sports.Test do {:ok, File.read!("test/data/timeline.xml")} end - defp fetch_mock_data(["sports", _lang, "competitors", _competitor, "profile.xml"]) do - {:ok, File.read!("test/data/competitor_profile.xml")} - end - defp fetch_mock_data(_endpoint) do {:error, "mock data not found"} end @@ -401,64 +397,4 @@ defmodule UOF.API.Sports.Test do test "can parse 'sports/:lang/tournaments/:tournament/seasons.xml' response" do # TO-DO end - - ## Entity descriptions - ## ========================================================================= - test "can parse UOF.API.Sports.competitor/{1, 2} response" do - {:ok, profile} = UOF.API.Sports.competitor("sr:competitor:2672") - - # competitor - assert profile.competitor.id == "sr:competitor:2672" - assert profile.competitor.name == "Bayern Munich" - assert profile.competitor.abbreviation == "BMU" - assert profile.competitor.country == "Germany" - assert profile.competitor.country_code == "DEU" - assert profile.competitor.gender == "male" - # competitor -> sport - assert profile.competitor.sport.id == "sr:sport:1" - assert profile.competitor.sport.name == "Soccer" - # competitor -> category - assert profile.competitor.category.id == "sr:category:30" - assert profile.competitor.category.name == "Germany" - assert profile.competitor.category.country_code == "DEU" - # venue - assert profile.venue.id == "sr:venue:574" - assert profile.venue.name == "Allianz Arena" - assert profile.venue.capacity == 75000 - assert profile.venue.city_name == "Munich" - assert profile.venue.country_name == "Germany" - assert profile.venue.country_code == "DEU" - assert profile.venue.map_coordinates == "48.218777,11.624748" - # jerseys - jersey = hd(profile.jerseys) - assert Enum.count(profile.jerseys) == 4 - assert jersey.type == "home" - assert jersey.base == "fdfcfc" - assert jersey.sleeve == "ff0000" - assert jersey.number == "f90606" - assert jersey.stripes == false - assert jersey.horizontal_stripes == false - assert jersey.squares == false - assert jersey.split == false - assert jersey.shirt_type == "short_sleeves" - # manager - assert profile.manager.id == "sr:player:72771" - assert profile.manager.name == "Tuchel, Thomas" - assert profile.manager.nationality == "Germany" - assert profile.manager.country_code == "DEU" - # players - player = hd(profile.players) - assert Enum.count(profile.players) == 32 - assert player.type == "goalkeeper" - assert player.date_of_birth == "1986-03-27" - assert player.nationality == "Germany" - assert player.country_code == "DEU" - assert player.height == 193 - assert player.weight == 93 - assert player.jersey_number == 1 - assert player.full_name == "Manuel Peter Neuer" - assert player.gender == "male" - assert player.id == "sr:player:8959" - assert player.name == "Neuer, Manuel" - end end From f92dab21f664d953a1a3b21b484cfa0ac36eedcd Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 19:37:21 +0200 Subject: [PATCH 27/34] dead code --- lib/uof/api/mappings/competitor_profile.ex | 20 -------------------- lib/uof/api/mappings/manager.ex | 11 ----------- 2 files changed, 31 deletions(-) delete mode 100644 lib/uof/api/mappings/competitor_profile.ex delete mode 100644 lib/uof/api/mappings/manager.ex diff --git a/lib/uof/api/mappings/competitor_profile.ex b/lib/uof/api/mappings/competitor_profile.ex deleted file mode 100644 index 21e739a..0000000 --- a/lib/uof/api/mappings/competitor_profile.ex +++ /dev/null @@ -1,20 +0,0 @@ -defmodule UOF.API.Mappings.CompetitorProfile do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.{Competitor, Jersey, Manager, Player, Venue} - - @type t :: %__MODULE__{ - competitor: Competitor.t(), - venue: Venue.t(), - jerseys: list(Jersey.t()), - players: list(Player.t()) - } - document do - element(:competitor, into: %Competitor{}) - element(:venue, into: %Venue{}) - element(:manager, into: %Manager{}) - elements(:jersey, as: :jerseys, into: %Jersey{}) - elements(:player, as: :players, into: %Player{}) - end -end diff --git a/lib/uof/api/mappings/manager.ex b/lib/uof/api/mappings/manager.ex deleted file mode 100644 index b45791a..0000000 --- a/lib/uof/api/mappings/manager.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule UOF.API.Mappings.Manager do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:id) - attribute(:name) - attribute(:nationality) - attribute(:country_code) - end -end From 79b8e69cd06a4da8e3be2ab1999522aa9a26aebc Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 19:37:44 +0200 Subject: [PATCH 28/34] dead code --- lib/uof/api/mappings/jersey.ex | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 lib/uof/api/mappings/jersey.ex diff --git a/lib/uof/api/mappings/jersey.ex b/lib/uof/api/mappings/jersey.ex deleted file mode 100644 index 64912a4..0000000 --- a/lib/uof/api/mappings/jersey.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule UOF.API.Mappings.Jersey do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:type) - attribute(:base) - attribute(:sleeve) - attribute(:number) - attribute(:stripes, cast: :boolean) - attribute(:horizontal_stripes, cast: :boolean) - attribute(:squares, cast: :boolean) - attribute(:split, cast: :boolean) - attribute(:shirt_type) - end -end From e942767d006f6a41b0440a2adb0a99c5bd8bb2a0 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sat, 25 May 2024 19:38:41 +0200 Subject: [PATCH 29/34] dead code --- lib/uof/api/mappings/player_profile.ex | 10 ---------- lib/uof/api/mappings/venue_profile.ex | 11 ----------- 2 files changed, 21 deletions(-) delete mode 100644 lib/uof/api/mappings/player_profile.ex delete mode 100644 lib/uof/api/mappings/venue_profile.ex diff --git a/lib/uof/api/mappings/player_profile.ex b/lib/uof/api/mappings/player_profile.ex deleted file mode 100644 index a3be9ce..0000000 --- a/lib/uof/api/mappings/player_profile.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.PlayerProfile do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.Player - - document do - element(:player, into: %Player{}) - end -end diff --git a/lib/uof/api/mappings/venue_profile.ex b/lib/uof/api/mappings/venue_profile.ex deleted file mode 100644 index 03c7a37..0000000 --- a/lib/uof/api/mappings/venue_profile.ex +++ /dev/null @@ -1,11 +0,0 @@ -defmodule UOF.API.Mappings.VenueProfile do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.{Competitor, Venue} - - document do - element(:venue, into: %Venue{}) - elements(:competitor, as: :home_teams, into: %Competitor{}) - end -end From 2c15c5e05851839936a64ee2191a6b59487f211a Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 26 May 2024 06:43:14 +0200 Subject: [PATCH 30/34] fixtures --- lib/uof/api/fixtures.ex | 35 ++++++-- lib/uof/api/fixtures/extra_info.ex | 21 +++++ lib/uof/api/fixtures/fixture.ex | 67 +++++++++++++++ lib/uof/api/fixtures/reference_id.ex | 21 +++++ lib/uof/api/fixtures/venue.ex | 32 -------- lib/uof/api/sports.ex | 46 ----------- lib/uof/api/tournaments/round.ex | 2 +- lib/uof/api/tournaments/season.ex | 3 +- lib/uof/api/tournaments/tournament_round.ex | 44 ++++++++++ test/uof/api/fixtures_test.exs | 90 +++++++++++++++++++++ test/uof/api/sports_test.exs | 79 ------------------ 11 files changed, 275 insertions(+), 165 deletions(-) create mode 100644 lib/uof/api/fixtures/extra_info.ex create mode 100644 lib/uof/api/fixtures/fixture.ex create mode 100644 lib/uof/api/fixtures/reference_id.ex delete mode 100644 lib/uof/api/fixtures/venue.ex create mode 100644 lib/uof/api/tournaments/tournament_round.ex create mode 100644 test/uof/api/fixtures_test.exs diff --git a/lib/uof/api/fixtures.ex b/lib/uof/api/fixtures.ex index 15d17c9..7d9f822 100644 --- a/lib/uof/api/fixtures.ex +++ b/lib/uof/api/fixtures.ex @@ -10,12 +10,6 @@ defmodule UOF.API.Fixtures do all(lang, 0, 1000, filter, map, []) end - # def changed() do - # end - - # def changed_results() do - # end - defp all(lang, start, limit, filter, map, acc) do case pre_schedule(lang, start, limit) do [] -> @@ -47,4 +41,33 @@ defmodule UOF.API.Fixtures do error end end + + # def changed() do + # end + + # def changed_results() do + # end + + @doc """ + Get the details of the given fixture. + """ + def show(id, lang \\ "en") do + # TO-DO: handle codds fixture (eg. codds:competition_group:77739) + case UOF.API.get("/sports/#{lang}/sport_events/#{id}/fixture.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("fixtures_fixture") + |> Map.get("fixture") + |> bubble_up("competitors", "competitor") + |> bubble_up("extra_info", "info") + |> bubble_up("reference_ids", "reference_id") + |> IO.inspect() + |> UOF.API.Fixtures.Fixture.changeset() + |> apply + + {:error, _} = error -> + error + end + end end diff --git a/lib/uof/api/fixtures/extra_info.ex b/lib/uof/api/fixtures/extra_info.ex new file mode 100644 index 0000000..b75d4db --- /dev/null +++ b/lib/uof/api/fixtures/extra_info.ex @@ -0,0 +1,21 @@ +defmodule UOF.API.Fixtures.ExtraInfo do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @type t :: %__MODULE__{ + key: String.t(), + value: String.t() + } + + @primary_key false + + embedded_schema do + field :key, :string + field :value, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, params, [:key, :value]) + end +end diff --git a/lib/uof/api/fixtures/fixture.ex b/lib/uof/api/fixtures/fixture.ex new file mode 100644 index 0000000..5c8b82c --- /dev/null +++ b/lib/uof/api/fixtures/fixture.ex @@ -0,0 +1,67 @@ +defmodule UOF.API.Fixtures.Fixture do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + # attribute(:start_time_confirmed, cast: :boolean) + # attribute(:start_time) + # attribute(:liveodds) + # attribute(:status) + # attribute(:next_live_time) + # attribute(:id) + # attribute(:scheduled) + # attribute(:start_time_tbd, cast: :boolean) + # elements(:competitor, as: :competitors, into: %Competitor{}) + # elements(:tv_channel, as: :tv_channels, into: %TVChannel{}) + # elements(:info, as: :extra_info, into: %ExtraInfo{}) + # elements(:reference_id, as: :references, into: %Reference{}) + # element(:tournament_round, into: %TournamentRound{}) + # element(:tournament, into: %Tournament{}) + # element(:season, into: %Season{}) + # element(:venue, into: %Venue{}) + # element(:coverage_info, into: %CoverageInfo{}) + # element(:product_info, into: %ProductInfo{}) + + @primary_key false + + embedded_schema do + field :start_time_confirmed, :boolean + field :start_time + field :liveodds + field :status + field :next_live_time + field :id + field :scheduled + field :start_time_tbd, :boolean + # field :tv_channels, {:array, :string} + embeds_one :season, UOF.API.Tournaments.Season + embeds_one :tournament, UOF.API.Sports.Tournament + embeds_one :tournament_round, UOF.API.Tournaments.TournamentRound + embeds_many :competitors, UOF.API.Competitors.Competitor + embeds_many :extra_info, UOF.API.Fixtures.ExtraInfo + embeds_many :reference_ids, UOF.API.Fixtures.ReferenceId + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :start_time_confirmed, + :start_time, + :liveodds, + :status, + :next_live_time, + :id, + :scheduled, + :start_time_tbd + # :tv_channels + ]) + |> cast_embed(:season) + |> cast_embed(:tournament) + |> cast_embed(:tournament_round) + |> cast_embed(:competitors) + |> cast_embed(:extra_info) + |> cast_embed(:reference_ids) + end +end diff --git a/lib/uof/api/fixtures/reference_id.ex b/lib/uof/api/fixtures/reference_id.ex new file mode 100644 index 0000000..55bac99 --- /dev/null +++ b/lib/uof/api/fixtures/reference_id.ex @@ -0,0 +1,21 @@ +defmodule UOF.API.Fixtures.ReferenceId do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @type t :: %__MODULE__{ + name: String.t(), + value: String.t() + } + + @primary_key false + + embedded_schema do + field :name, :string + field :value, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, params, [:name, :value]) + end +end diff --git a/lib/uof/api/fixtures/venue.ex b/lib/uof/api/fixtures/venue.ex deleted file mode 100644 index 5e68c3c..0000000 --- a/lib/uof/api/fixtures/venue.ex +++ /dev/null @@ -1,32 +0,0 @@ -defmodule UOF.API.Fixtures.Venue do - @moduledoc false - - use Ecto.Schema - import Ecto.Changeset - import UOF.API.EctoHelpers - - @primary_key false - - embedded_schema do - field :id, :string - field :name, :string - field :capacity, :integer - field :city_name, :string - field :country_name, :string - field :map_coordinates, :string - field :country_code, :string - end - - def changeset(model \\ %__MODULE__{}, params) do - model - |> cast(params, [ - :id, - :name, - :capacity, - :city_name, - :country_name, - :country_code, - :map_coordinates - ]) - end -end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index 610206b..16612d6 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -50,53 +50,7 @@ defmodule UOF.API.Sports do end end - ## Auxiliary functions ## ========================================================================= - def fixtures(filter \\ & &1, map \\ & &1) do - fixtures(0, 1000, filter, map, []) - end - - defp fixtures(start, limit, filter, map, acc) do - {:ok, schedule} = pre_schedule(start, limit) - - case schedule.events do - [] -> - acc - - events -> - # TO-DO: Return subset of fields (eg. only fixture ids) - # events = maybe_filter_fixtures(events, filter) - events = for e <- events, filter.(e), do: map.(e) - fixtures(start + limit, limit, filter, map, events ++ acc) - end - end - - # defp maybe_filter_fixtures(fixtures, nil) do - # fixtures - # end - # defp maybe_filter_fixtures(fixtures, filter) do - # Enum.filter(fixtures, filter) - # end - - def fixtures_by_sport(sports) when is_list(sports) do - fixtures(&(&1.tournament.sport.name in sports)) - end - - def fixtures_by_sport(sport) do - fixtures_by_sport([sport]) - end - - ## ========================================================================= - - @doc """ - Get the details of the given fixture. - """ - def fixture(fixture, lang \\ "en") do - # TO-DO: handle codds fixture (eg. codds:competition_group:77739) - endpoint = ["sports", lang, "sport_events", fixture, "fixture.xml"] - - HTTP.get(%UOF.API.Mappings.FixturesFixture{}, endpoint) - end @doc """ Get a list of all the fixtures scheduled to start at the given date (in UTC). diff --git a/lib/uof/api/tournaments/round.ex b/lib/uof/api/tournaments/round.ex index 6c788e5..c2d8516 100644 --- a/lib/uof/api/tournaments/round.ex +++ b/lib/uof/api/tournaments/round.ex @@ -13,7 +13,7 @@ defmodule UOF.API.Tournaments.Round do field :cup_round_match_number, :integer end - def changeset(%__MODULE__{} = model, params) do + def changeset(model \\ %__MODULE__{}, params) do model |> cast(params, [:type, :name, :number, :cup_round_matches, :cup_round_match_number]) end diff --git a/lib/uof/api/tournaments/season.ex b/lib/uof/api/tournaments/season.ex index b3fa6ad..bc80530 100644 --- a/lib/uof/api/tournaments/season.ex +++ b/lib/uof/api/tournaments/season.ex @@ -11,10 +11,11 @@ defmodule UOF.API.Tournaments.Season do field :year, :string field :id, :string field :name, :string + field :tournament_id, :string end def changeset(%__MODULE__{} = model, params) do model - |> cast(params, [:id, :name, :start_date, :end_date, :year]) + |> cast(params, [:id, :name, :start_date, :end_date, :year, :tournament_id]) end end diff --git a/lib/uof/api/tournaments/tournament_round.ex b/lib/uof/api/tournaments/tournament_round.ex new file mode 100644 index 0000000..6125bfa --- /dev/null +++ b/lib/uof/api/tournaments/tournament_round.ex @@ -0,0 +1,44 @@ +defmodule UOF.API.Tournaments.TournamentRound do + @moduledoc false + use Ecto.Schema + import Ecto.Changeset + + @primary_key false + + embedded_schema do + field :betradar_id, :integer + field :betradar_name + field :type, :string + # cup + field :name, :string + field :cup_round_matches, :integer + field :cup_round_match_number, :integer + field :other_match_id + # group + field :number + field :group + field :group_id + field :group_long_name + # qualification + # ??? + field :phase + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, [ + :betradar_id, + :betradar_name, + :type, + :name, + :number, + :cup_round_matches, + :cup_round_match_number, + :other_match_id, + :group, + :group_id, + :group_long_name, + :phase + ]) + end +end diff --git a/test/uof/api/fixtures_test.exs b/test/uof/api/fixtures_test.exs new file mode 100644 index 0000000..31076fb --- /dev/null +++ b/test/uof/api/fixtures_test.exs @@ -0,0 +1,90 @@ +defmodule UOF.API.Fixtures.Test do + use ExUnit.Case + + setup do + :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) + + Tesla.Mock.mock(fn + %{method: :get} -> + resp = File.read!("test/data/fixture.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + end) + + :ok + end + + test "can parse UOF.API.Fixtures.show/{1, 2} response" do + {:ok, fixture} = UOF.API.Fixtures.show("sr:match:8696826") + + # fixture attributes + assert fixture.start_time_confirmed == true + assert fixture.liveodds == "not_available" + assert fixture.status == "closed" + assert fixture.next_live_time == "2016-10-31T18:00:00+00:00" + assert fixture.id == "sr:match:8696826" + assert fixture.scheduled == "2016-10-31T18:00:00+00:00" + assert fixture.start_time_tbd == false + # tournament round + assert fixture.tournament_round.type == "group" + assert fixture.tournament_round.number == "25" + assert fixture.tournament_round.group_long_name == "Ettan, Sodra" + assert fixture.tournament_round.betradar_name == "Ettan, Sodra" + assert fixture.tournament_round.betradar_id == 4301 + # season + assert fixture.season.start_date == "2016-04-16" + assert fixture.season.end_date == "2016-11-05" + assert fixture.season.year == "2016" + assert fixture.season.tournament_id == "sr:tournament:68" + assert fixture.season.id == "sr:season:12346" + assert fixture.season.name == "Div 1, Sodra 2016" + # tournament + assert fixture.tournament.id == "sr:tournament:68" + assert fixture.tournament.name == "Ettan, Sodra" + assert fixture.tournament.sport.id == "sr:sport:1" + assert fixture.tournament.sport.name == "Soccer" + assert fixture.tournament.category.id == "sr:category:9" + assert fixture.tournament.category.name == "Sweden" + # competitors + [competitor1, competitor2] = fixture.competitors + assert competitor1.qualifier == "home" + assert competitor1.id == "sr:competitor:1860" + assert competitor1.name == "IK Oddevold" + assert competitor1.abbreviation == "ODD" + assert competitor1.short_name == "Oddevold" + assert competitor1.country == "Sweden" + assert competitor1.country_code == "SWE" + assert competitor1.gender == "male" + assert competitor2.qualifier == "away" + assert competitor2.id == "sr:competitor:22356" + assert competitor2.name == "Tvaakers IF" + assert competitor2.abbreviation == "TVA" + assert competitor2.short_name == "Tvaakers" + assert competitor2.country == "Sweden" + assert competitor2.country_code == "SWE" + assert competitor2.gender == "male" + # tv channels + # assert fixture.tv_channels == [] + # extra info + [info1, info2, info3, info4, info5, info6, info7] = fixture.extra_info + assert info1.key == "RTS" + assert info1.value == "not_available" + assert info2.key == "coverage_source" + assert info2.value == "venue" + assert info3.key == "extended_live_markets_offered" + assert info3.value == "false" + assert info4.key == "streaming" + assert info4.value == "false" + assert info5.key == "auto_traded" + assert info5.value == "false" + assert info6.key == "neutral_ground" + assert info6.value == "false" + assert info7.key == "period_length" + assert info7.value == "45" + # product info + # TO-DO: implement + # reference ids + [reference] = fixture.reference_ids + assert reference.name == "BetradarCtrl" + assert reference.value == "11428313" + end +end diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index be0d610..7cd9b0e 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -15,10 +15,6 @@ defmodule UOF.API.Sports.Test do :ok end - defp fetch_mock_data(["sports", _lang, "sport_events", _fixture, "fixture.xml"]) do - {:ok, File.read!("test/data/fixture.xml")} - end - defp fetch_mock_data(["sports", _lang, "fixtures", "changes.xml"]) do {:ok, File.read!("test/data/fixture_changes.xml")} end @@ -42,81 +38,6 @@ defmodule UOF.API.Sports.Test do ## Static sport event information ## ========================================================================= - test "can parse UOF.API.Sports.fixture/{1, 2} response" do - {:ok, ff} = UOF.API.Sports.fixture("sr:match:8696826") - - # fixture attributes - assert ff.fixture.start_time_confirmed == true - assert ff.fixture.liveodds == "not_available" - assert ff.fixture.status == "closed" - assert ff.fixture.next_live_time == "2016-10-31T18:00:00+00:00" - assert ff.fixture.id == "sr:match:8696826" - assert ff.fixture.scheduled == "2016-10-31T18:00:00+00:00" - assert ff.fixture.start_time_tbd == false - # tournament round - assert ff.fixture.tournament_round.type == "group" - assert ff.fixture.tournament_round.number == "25" - assert ff.fixture.tournament_round.group_long_name == "Ettan, Sodra" - assert ff.fixture.tournament_round.betradar_name == "Ettan, Sodra" - assert ff.fixture.tournament_round.betradar_id == 4301 - # season - assert ff.fixture.season.start_date == "2016-04-16" - assert ff.fixture.season.end_date == "2016-11-05" - assert ff.fixture.season.year == "2016" - assert ff.fixture.season.tournament_id == "sr:tournament:68" - assert ff.fixture.season.id == "sr:season:12346" - assert ff.fixture.season.name == "Div 1, Sodra 2016" - # tournament - assert ff.fixture.tournament.id == "sr:tournament:68" - assert ff.fixture.tournament.name == "Ettan, Sodra" - assert ff.fixture.tournament.sport.id == "sr:sport:1" - assert ff.fixture.tournament.sport.name == "Soccer" - assert ff.fixture.tournament.category.id == "sr:category:9" - assert ff.fixture.tournament.category.name == "Sweden" - # competitors - [competitor1, competitor2] = ff.fixture.competitors - assert competitor1.qualifier == "home" - assert competitor1.id == "sr:competitor:1860" - assert competitor1.name == "IK Oddevold" - assert competitor1.abbreviation == "ODD" - assert competitor1.short_name == "Oddevold" - assert competitor1.country == "Sweden" - assert competitor1.country_code == "SWE" - assert competitor1.gender == "male" - assert competitor2.qualifier == "away" - assert competitor2.id == "sr:competitor:22356" - assert competitor2.name == "Tvaakers IF" - assert competitor2.abbreviation == "TVA" - assert competitor2.short_name == "Tvaakers" - assert competitor2.country == "Sweden" - assert competitor2.country_code == "SWE" - assert competitor2.gender == "male" - # tv channels - assert ff.fixture.tv_channels == [] - # extra info - [info1, info2, info3, info4, info5, info6, info7] = ff.fixture.extra_info - assert info1.key == "RTS" - assert info1.value == "not_available" - assert info2.key == "coverage_source" - assert info2.value == "venue" - assert info3.key == "extended_live_markets_offered" - assert info3.value == "false" - assert info4.key == "streaming" - assert info4.value == "false" - assert info5.key == "auto_traded" - assert info5.value == "false" - assert info6.key == "neutral_ground" - assert info6.value == "false" - assert info7.key == "period_length" - assert info7.value == "45" - # product info - # TO-DO: implement - # reference ids - [reference] = ff.fixture.references - assert reference.name == "BetradarCtrl" - assert reference.value == "11428313" - end - test "can parse UOF.API.Sports.fixture_changes/{0, 1} response" do {:ok, data} = UOF.API.Sports.fixture_changes() From 3896d9d7209ba1bbdee565a3290e34a669aae263 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 26 May 2024 06:44:02 +0200 Subject: [PATCH 31/34] remove vale job --- .github/workflows/vale.yml | 26 -------------------------- 1 file changed, 26 deletions(-) delete mode 100644 .github/workflows/vale.yml diff --git a/.github/workflows/vale.yml b/.github/workflows/vale.yml deleted file mode 100644 index d52c7be..0000000 --- a/.github/workflows/vale.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: vale - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -on: [pull_request] - -permissions: - pull-requests: write - -jobs: - vale: - name: Prose linter - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: errata-ai/vale-action@reviewdog - with: - version: 3.4.2 - filter_mode: diff_context - fail_on_error: true - reporter: github-pr-review - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 39dd3b410ea34f2ae869acd3de1f0b319bfe1221 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 26 May 2024 19:09:00 +0200 Subject: [PATCH 32/34] fixtures --- lib/uof/api/ecto_helpers.ex | 2 +- lib/uof/api/fixtures.ex | 95 ++++++++++++++++++++++++++++++-- lib/uof/api/fixtures/change.ex | 21 +++++++ lib/uof/api/fixtures/summary.ex | 22 ++++++++ lib/uof/api/fixtures/timeline.ex | 22 ++++++++ lib/uof/api/sports.ex | 45 --------------- test/uof/api/fixtures_test.exs | 30 +++++++++- test/uof/api/sports_test.exs | 28 ---------- 8 files changed, 186 insertions(+), 79 deletions(-) create mode 100644 lib/uof/api/fixtures/change.ex create mode 100644 lib/uof/api/fixtures/summary.ex create mode 100644 lib/uof/api/fixtures/timeline.ex diff --git a/lib/uof/api/ecto_helpers.ex b/lib/uof/api/ecto_helpers.ex index 03e5fc2..7fe28d4 100644 --- a/lib/uof/api/ecto_helpers.ex +++ b/lib/uof/api/ecto_helpers.ex @@ -35,7 +35,7 @@ defmodule UOF.API.EctoHelpers do Map.put(params, field, values) end - def rename(params, old, new, default) do + def rename(params, old, new, default \\ nil) do {values, params} = Map.pop(params, old, default) Map.put(params, new, values) end diff --git a/lib/uof/api/fixtures.ex b/lib/uof/api/fixtures.ex index 7d9f822..ce84d28 100644 --- a/lib/uof/api/fixtures.ex +++ b/lib/uof/api/fixtures.ex @@ -42,11 +42,46 @@ defmodule UOF.API.Fixtures do end end - # def changed() do - # end + @doc """ + Get a list of all the fixtures that have changed in the last 24 hours. + """ + def changed(lang \\ "en") do + case UOF.API.get("/sports/#{lang}/fixtures/changes.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("fixture_changes") + |> Map.get("fixture_change") + |> Enum.map(fn x -> + {:ok, x} = apply(UOF.API.Fixtures.Change.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end - # def changed_results() do - # end + @doc """ + Get a lists of all the fixtures that have changed results in the last 24 hours. + """ + def changed_result(lang \\ "en") do + # TO-DO: add support for 'after datetime' and 'sport' filters + case UOF.API.get("/sports/#{lang}/results/changes.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> Map.get("result_changes") + |> Map.get("result_change") + |> Enum.map(fn x -> + {:ok, x} = apply(UOF.API.Fixtures.Change.changeset(x)) + x + end) + + {:error, _} = error -> + error + end + end @doc """ Get the details of the given fixture. @@ -70,4 +105,56 @@ defmodule UOF.API.Fixtures do error end end + + @doc """ + Get information and results for the given fixture. + """ + def summary(id, lang \\ "en") do + # https://docs.betradar.com/display/BD/UOF+-+Summary+end+point + # TO-DO: differentiate between match and race summaries + case UOF.API.get("/sports/#{lang}/sport_events/#{id}/summary.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> IO.inspect() + |> Map.get("match_summary") + # |> Map.get("fixture") + # |> bubble_up("competitors", "competitor") + # |> bubble_up("extra_info", "info") + # |> bubble_up("reference_ids", "reference_id") + |> rename("sport_event", "fixture") + + # |> UOF.API.Fixtures.Fixture.Summary.changeset() + # |> apply + + {:error, _} = error -> + error + end + end + + @doc """ + Get detailed information (including event timeline) for the given sport event. + # Prematch, Live or Post-match. Prematch details are very brief. Post-match + # details include results. + """ + def timeline(id, lang \\ "en") do + case UOF.API.get("/sports/#{lang}/sport_events/#{id}/timeline.xml") do + {:ok, %_{status: 200, body: resp}} -> + resp + |> UOF.API.Utils.xml_to_map() + |> IO.inspect() + |> Map.get("match_timeline") + |> rename("sport_event", "fixture") + + # |> bubble_up("competitors", "competitor") + # |> bubble_up("extra_info", "info") + # |> bubble_up("reference_ids", "reference_id") + # |> IO.inspect() + # |> UOF.API.Fixtures.Fixture.Summary.changeset() + # |> apply + + {:error, _} = error -> + error + end + end end diff --git a/lib/uof/api/fixtures/change.ex b/lib/uof/api/fixtures/change.ex new file mode 100644 index 0000000..6e9eff7 --- /dev/null +++ b/lib/uof/api/fixtures/change.ex @@ -0,0 +1,21 @@ +defmodule UOF.API.Fixtures.Change do + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + @type t :: %__MODULE__{ + sport_event_id: String.t(), + update_time: String.t() + } + + @primary_key false + + embedded_schema do + field :sport_event_id, :string + field :update_time, :string + end + + def changeset(model \\ %__MODULE__{}, params) do + cast(model, params, [:sport_event_id, :update_time]) + end +end diff --git a/lib/uof/api/fixtures/summary.ex b/lib/uof/api/fixtures/summary.ex new file mode 100644 index 0000000..2e160c4 --- /dev/null +++ b/lib/uof/api/fixtures/summary.ex @@ -0,0 +1,22 @@ +defmodule UOF.API.Fixtures.Summary do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + # element(:sport_event, into: %SportEvent{}) + # element(:sport_event_conditions, as: :event_conditions, into: %EventConditions{}) + # element(:sport_event_status, as: :event_status, into: %SportEventStatus{}) + # element(:coverage_info, into: %CoverageInfo{}) + + @primary_key false + + embedded_schema do + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, []) + end +end diff --git a/lib/uof/api/fixtures/timeline.ex b/lib/uof/api/fixtures/timeline.ex new file mode 100644 index 0000000..24c25bf --- /dev/null +++ b/lib/uof/api/fixtures/timeline.ex @@ -0,0 +1,22 @@ +defmodule UOF.API.Fixtures.Timeline do + @moduledoc false + + use Ecto.Schema + import Ecto.Changeset + import UOF.API.EctoHelpers + + # element(:sport_event, into: %SportEvent{}) + # element(:sport_event_conditions, as: :event_conditions, into: %EventConditions{}) + # element(:sport_event_status, as: :event_status, into: %SportEventStatus{}) + # element(:coverage_info, into: %CoverageInfo{}) + + @primary_key false + + embedded_schema do + end + + def changeset(model \\ %__MODULE__{}, params) do + model + |> cast(params, []) + end +end diff --git a/lib/uof/api/sports.ex b/lib/uof/api/sports.ex index 16612d6..2b2cb24 100644 --- a/lib/uof/api/sports.ex +++ b/lib/uof/api/sports.ex @@ -78,49 +78,4 @@ defmodule UOF.API.Sports do HTTP.get(%UOF.API.Mappings.Schedule{}, endpoint) end - - @doc """ - Get a list of all the fixtures that have changed in the last 24 hours. - """ - def fixture_changes(lang \\ "en") do - # TO-DO: add support for 'after datetime' and 'sport' filters - endpoint = ["sports", lang, "fixtures", "changes.xml"] - - HTTP.get(%UOF.API.Mappings.FixtureChanges{}, endpoint) - end - - @doc """ - Get a lists of all the fixtures that have changed results in the last 24 hours. - """ - def result_changes(lang \\ "en") do - # TO-DO: add support for 'after datetime' and 'sport' filters - endpoint = ["sports", lang, "results", "changes.xml"] - - HTTP.get(%UOF.API.Mappings.ResultChanges{}, endpoint) - end - - ## Sport Event Information - ## ========================================================================= - - @doc """ - Get information and results for the given fixture. - """ - def summary(fixture, lang \\ "en") do - # https://docs.betradar.com/display/BD/UOF+-+Summary+end+point - # TO-DO: differentiate between match and race summaries - endpoint = ["sports", lang, "sport_events", fixture, "summary.xml"] - - HTTP.get(%UOF.API.Mappings.Summary{}, endpoint) - end - - @doc """ - Get detailed information (including event timeline) for the given sport event. - # Prematch, Live or Post-match. Prematch details are very brief. Post-match - # details include results. - """ - def timeline(fixture, lang \\ "en") do - endpoint = ["sports", lang, "sport_events", fixture, "timeline.xml"] - - HTTP.get(%UOF.API.Mappings.Timeline{}, endpoint) - end end diff --git a/test/uof/api/fixtures_test.exs b/test/uof/api/fixtures_test.exs index 31076fb..57952eb 100644 --- a/test/uof/api/fixtures_test.exs +++ b/test/uof/api/fixtures_test.exs @@ -5,9 +5,17 @@ defmodule UOF.API.Fixtures.Test do :ok = Application.put_env(:tesla, UOF.API, adapter: Tesla.Mock) Tesla.Mock.mock(fn - %{method: :get} -> + %{method: :get, url: "/sports/en/sport_events/sr:match:8696826/fixture.xml"} -> resp = File.read!("test/data/fixture.xml") %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + + %{method: :get, url: "/sports/en/fixtures/changes.xml"} -> + resp = File.read!("test/data/fixture_changes.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} + + %{method: :get, url: "/sports/en/results/changes.xml"} -> + resp = File.read!("test/data/result_changes.xml") + %Tesla.Env{status: 200, headers: [{"content-type", "application/xml"}], body: resp} end) :ok @@ -87,4 +95,24 @@ defmodule UOF.API.Fixtures.Test do assert reference.name == "BetradarCtrl" assert reference.value == "11428313" end + + test "can parse UOF.API.Fixtures.changed/{0, 1} response" do + changes = UOF.API.Fixtures.changed() + + change = hd(changes) + + assert Enum.count(changes) == 9543 + assert change.sport_event_id == "sr:match:49540297" + assert change.update_time == "2024-04-26T19:12:43+00:00" + end + + test "can parse UOF.API.Fixtures.changed_results/{0, 1} response" do + changes = UOF.API.Fixtures.changed_result() + + change = hd(changes) + + assert Enum.count(changes) == 4236 + assert change.sport_event_id == "sr:match:49689671" + assert change.update_time == "2024-04-26T19:12:56+00:00" + end end diff --git a/test/uof/api/sports_test.exs b/test/uof/api/sports_test.exs index 7cd9b0e..ca75cdb 100644 --- a/test/uof/api/sports_test.exs +++ b/test/uof/api/sports_test.exs @@ -15,14 +15,6 @@ defmodule UOF.API.Sports.Test do :ok end - defp fetch_mock_data(["sports", _lang, "fixtures", "changes.xml"]) do - {:ok, File.read!("test/data/fixture_changes.xml")} - end - - defp fetch_mock_data(["sports", _lang, "results", "changes.xml"]) do - {:ok, File.read!("test/data/result_changes.xml")} - end - defp fetch_mock_data(["sports", _lang, "sport_events", _fixture, "summary.xml"]) do {:ok, File.read!("test/data/summary.xml")} end @@ -38,26 +30,6 @@ defmodule UOF.API.Sports.Test do ## Static sport event information ## ========================================================================= - test "can parse UOF.API.Sports.fixture_changes/{0, 1} response" do - {:ok, data} = UOF.API.Sports.fixture_changes() - - change = hd(data.changes) - - assert Enum.count(data.changes) == 9543 - assert change.sport_event_id == "sr:match:49540297" - assert change.update_time == "2024-04-26T19:12:43+00:00" - end - - test "can parse UOF.API.Sports.result_changes/{0, 1} response" do - {:ok, data} = UOF.API.Sports.result_changes() - - change = hd(data.changes) - - assert Enum.count(data.changes) == 4236 - assert change.sport_event_id == "sr:match:49689671" - assert change.update_time == "2024-04-26T19:12:56+00:00" - end - ## Sport event information ## ========================================================================= test "can parse 'sports/:lang/sport_events/:fixture/summary.xml' response" do From e8b55155a230dd51d4eb1424b6258aaf4b6aad06 Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 26 May 2024 19:10:08 +0200 Subject: [PATCH 33/34] dead code --- lib/uof/api/mappings/fixture_change.ex | 9 --------- lib/uof/api/mappings/fixture_changes.ex | 10 ---------- lib/uof/api/mappings/result_change.ex | 9 --------- lib/uof/api/mappings/result_changes.ex | 10 ---------- 4 files changed, 38 deletions(-) delete mode 100644 lib/uof/api/mappings/fixture_change.ex delete mode 100644 lib/uof/api/mappings/fixture_changes.ex delete mode 100644 lib/uof/api/mappings/result_change.ex delete mode 100644 lib/uof/api/mappings/result_changes.ex diff --git a/lib/uof/api/mappings/fixture_change.ex b/lib/uof/api/mappings/fixture_change.ex deleted file mode 100644 index 6a75ea5..0000000 --- a/lib/uof/api/mappings/fixture_change.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule UOF.API.Mappings.FixtureChange do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:sport_event_id) - attribute(:update_time) - end -end diff --git a/lib/uof/api/mappings/fixture_changes.ex b/lib/uof/api/mappings/fixture_changes.ex deleted file mode 100644 index 419e35f..0000000 --- a/lib/uof/api/mappings/fixture_changes.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.FixtureChanges do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.FixtureChange - - document do - elements(:fixture_change, as: :changes, into: %FixtureChange{}) - end -end diff --git a/lib/uof/api/mappings/result_change.ex b/lib/uof/api/mappings/result_change.ex deleted file mode 100644 index 3e8bf09..0000000 --- a/lib/uof/api/mappings/result_change.ex +++ /dev/null @@ -1,9 +0,0 @@ -defmodule UOF.API.Mappings.ResultChange do - @moduledoc false - use Saxaboom.Mapper - - document do - attribute(:sport_event_id) - attribute(:update_time) - end -end diff --git a/lib/uof/api/mappings/result_changes.ex b/lib/uof/api/mappings/result_changes.ex deleted file mode 100644 index ee7f622..0000000 --- a/lib/uof/api/mappings/result_changes.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule UOF.API.Mappings.ResultChanges do - @moduledoc false - use Saxaboom.Mapper - - alias UOF.API.Mappings.ResultChange - - document do - elements(:result_change, as: :changes, into: %ResultChange{}) - end -end From 78424febd88a56a6768277dea3cfab9f3cb1757b Mon Sep 17 00:00:00 2001 From: Enrique Fernandez Date: Sun, 26 May 2024 19:16:36 +0200 Subject: [PATCH 34/34] remove vale config --- .vale.ini | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .vale.ini diff --git a/.vale.ini b/.vale.ini deleted file mode 100644 index 9cd14ea..0000000 --- a/.vale.ini +++ /dev/null @@ -1,5 +0,0 @@ -StylesPath = .github/styles -MinAlertLevel = suggestion - -[README.md] -BasedOnStyles = Vale \ No newline at end of file