jumbotron 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +21 -0
- data/README.md +5 -0
- data/app/adapters/jumbotron/adapters/base.rb +136 -0
- data/app/adapters/jumbotron/adapters/cadence.rb +42 -0
- data/app/adapters/jumbotron/adapters/discovery_definition.rb +7 -0
- data/app/adapters/jumbotron/adapters/espn/nfl.rb +169 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/acquisition_scope.rb +29 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/competition_odds.rb +185 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/lifecycle.rb +30 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/line_acquisition_scope.rb +26 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/progress.rb +54 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/scoreboard.rb +146 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/season_phase.rb +33 -0
- data/app/adapters/jumbotron/adapters/espn/nfl_helpers/teams.rb +35 -0
- data/app/adapters/jumbotron/adapters/line_acquisition.rb +19 -0
- data/app/adapters/jumbotron/adapters/operation.rb +23 -0
- data/app/adapters/jumbotron/adapters/policy_definition.rb +14 -0
- data/app/adapters/jumbotron/adapters/registry.rb +78 -0
- data/app/adapters/jumbotron/adapters.rb +11 -0
- data/app/boot/jumbotron/boot/enqueue_discoveries.rb +41 -0
- data/app/clients/jumbotron/clients/espn/competition_odds/get.rb +26 -0
- data/app/clients/jumbotron/clients/espn/competition_odds/get_input.rb +16 -0
- data/app/clients/jumbotron/clients/espn/scoreboard/get.rb +18 -0
- data/app/clients/jumbotron/clients/espn/scoreboard/get_input.rb +17 -0
- data/app/clients/jumbotron/clients/espn/teams/get.rb +18 -0
- data/app/clients/jumbotron/clients/espn/teams/get_input.rb +15 -0
- data/app/clients/jumbotron/clients/espn.rb +44 -0
- data/app/clients/jumbotron/clients.rb +8 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition.rb +79 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/draw_odds.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/get.rb +66 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/item.rb +60 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/listed_provider.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/market_window.rb +31 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/point_spread.rb +27 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/price.rb +35 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/side_quote.rb +35 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competition_odds/team_odds.rb +40 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/competitor.rb +49 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/event.rb +81 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/scoreboard/get.rb +88 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/season.rb +27 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/status.rb +69 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/support.rb +61 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/team.rb +42 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/teams/get.rb +76 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/venue.rb +34 -0
- data/app/deserializers/jumbotron/deserializers/clients/espn/week.rb +23 -0
- data/app/deserializers/jumbotron/deserializers/public/game.rb +26 -0
- data/app/deserializers/jumbotron/deserializers/public/game_ids.rb +28 -0
- data/app/deserializers/jumbotron/deserializers/public/includes.rb +32 -0
- data/app/deserializers/jumbotron/deserializers/public/schedule.rb +151 -0
- data/app/deserializers/jumbotron/deserializers/public/schedule_groups.rb +91 -0
- data/app/errors/jumbotron/error.rb +6 -0
- data/app/errors/jumbotron/errors/adapters.rb +143 -0
- data/app/errors/jumbotron/errors/canonical.rb +67 -0
- data/app/errors/jumbotron/errors/public.rb +27 -0
- data/app/errors/jumbotron/invalid_request_error.rb +6 -0
- data/app/errors/jumbotron/not_found_error.rb +6 -0
- data/app/errors/jumbotron/unsupported_capability_error.rb +6 -0
- data/app/jobs/jumbotron/application_job.rb +6 -0
- data/app/jobs/jumbotron/discovery_job.rb +14 -0
- data/app/jobs/jumbotron/game_update_job.rb +12 -0
- data/app/jobs/jumbotron/line_update_job.rb +12 -0
- data/app/models/jumbotron/application_record.rb +8 -0
- data/app/models/jumbotron/bookmaker.rb +11 -0
- data/app/models/jumbotron/canonical.rb +128 -0
- data/app/models/jumbotron/game.rb +92 -0
- data/app/models/jumbotron/game_participant.rb +10 -0
- data/app/models/jumbotron/historical_change.rb +9 -0
- data/app/models/jumbotron/league.rb +12 -0
- data/app/models/jumbotron/line_observation.rb +18 -0
- data/app/models/jumbotron/observation_batch.rb +14 -0
- data/app/models/jumbotron/provider_identity.rb +32 -0
- data/app/models/jumbotron/public/translate_outcome.rb +57 -0
- data/app/models/jumbotron/public.rb +88 -0
- data/app/models/jumbotron/schedule_group.rb +22 -0
- data/app/models/jumbotron/season.rb +13 -0
- data/app/models/jumbotron/season_phase.rb +12 -0
- data/app/models/jumbotron/sport.rb +10 -0
- data/app/models/jumbotron/team.rb +11 -0
- data/app/models/jumbotron/venue.rb +10 -0
- data/app/providers/jumbotron/providers/espn/cooldown.rb +88 -0
- data/app/providers/jumbotron/providers/espn/cooldown_active_error.rb +13 -0
- data/app/providers/jumbotron/providers/espn/provider_failure.rb +40 -0
- data/app/providers/jumbotron/providers/espn/resources/competition_odds.rb +29 -0
- data/app/providers/jumbotron/providers/espn/resources/scoreboard.rb +90 -0
- data/app/providers/jumbotron/providers/espn/resources/teams.rb +19 -0
- data/app/scheduling/jumbotron/scheduling/backends/sidekiq_cron.rb +70 -0
- data/app/scheduling/jumbotron/scheduling/backends/solid_queue.rb +92 -0
- data/app/scheduling/jumbotron/scheduling/clock.rb +61 -0
- data/app/scheduling/jumbotron/scheduling/desired_schedules.rb +51 -0
- data/app/scheduling/jumbotron/scheduling/detect_integration.rb +45 -0
- data/app/scheduling/jumbotron/scheduling/materializer.rb +13 -0
- data/app/scheduling/jumbotron/scheduling/owned.rb +19 -0
- data/app/scheduling/jumbotron/scheduling/schedule_definition.rb +7 -0
- data/app/serializers/jumbotron/serializers/clients/espn/competition_odds/get.rb +17 -0
- data/app/serializers/jumbotron/serializers/clients/espn/scoreboard/get.rb +22 -0
- data/app/serializers/jumbotron/serializers/clients/espn/teams/get.rb +20 -0
- data/app/services/jumbotron/services/adapters/build_acquisition_scopes.rb +37 -0
- data/app/services/jumbotron/services/adapters/build_line_acquisition_scopes.rb +39 -0
- data/app/services/jumbotron/services/adapters/ensure_league.rb +29 -0
- data/app/services/jumbotron/services/adapters/execute_endpoint.rb +49 -0
- data/app/services/jumbotron/services/adapters/execute_line_acquisition_scope.rb +75 -0
- data/app/services/jumbotron/services/adapters/execute_synchronization_scope.rb +64 -0
- data/app/services/jumbotron/services/adapters/resolve_discovery.rb +53 -0
- data/app/services/jumbotron/services/adapters/resolve_game_update_policy.rb +74 -0
- data/app/services/jumbotron/services/adapters/resolve_line_update_policy.rb +74 -0
- data/app/services/jumbotron/services/adapters/select_eligible_games.rb +41 -0
- data/app/services/jumbotron/services/adapters/validate_sync_request.rb +51 -0
- data/app/services/jumbotron/services/canonical/attach_provider_identities.rb +59 -0
- data/app/services/jumbotron/services/canonical/derive_consensus_for_games.rb +18 -0
- data/app/services/jumbotron/services/canonical/derive_consensus_line.rb +70 -0
- data/app/services/jumbotron/services/canonical/ensure_bookmaker.rb +70 -0
- data/app/services/jumbotron/services/canonical/ensure_schedule_group.rb +40 -0
- data/app/services/jumbotron/services/canonical/ensure_season.rb +22 -0
- data/app/services/jumbotron/services/canonical/ensure_season_phase.rb +22 -0
- data/app/services/jumbotron/services/canonical/persist_line_observations.rb +145 -0
- data/app/services/jumbotron/services/canonical/record_observation_evidence.rb +51 -0
- data/app/services/jumbotron/services/canonical/resolve_current_lines.rb +21 -0
- data/app/services/jumbotron/services/canonical/resolve_current_lines_for_games.rb +69 -0
- data/app/services/jumbotron/services/canonical/upsert_game.rb +186 -0
- data/app/services/jumbotron/services/canonical/upsert_game_participant.rb +88 -0
- data/app/services/jumbotron/services/canonical/upsert_team.rb +90 -0
- data/app/services/jumbotron/services/canonical/upsert_venue.rb +92 -0
- data/app/services/jumbotron/services/public/assemble_public_game.rb +131 -0
- data/app/services/jumbotron/services/public/assemble_public_schedule.rb +63 -0
- data/app/services/jumbotron/services/public/derive_consensus_for_games.rb +21 -0
- data/app/services/jumbotron/services/public/load_games_by_ids.rb +18 -0
- data/app/services/jumbotron/services/public/load_included_lines.rb +47 -0
- data/app/services/jumbotron/services/public/map_public_consensus_lines.rb +44 -0
- data/app/services/jumbotron/services/public/map_public_current_lines.rb +27 -0
- data/app/services/jumbotron/services/public/read_game.rb +32 -0
- data/app/services/jumbotron/services/public/read_schedule.rb +116 -0
- data/app/services/jumbotron/services/public/read_schedule_groups.rb +90 -0
- data/app/services/jumbotron/services/public/resolve_current_lines_for_games.rb +21 -0
- data/app/synchronization/jumbotron/synchronization/lease.rb +127 -0
- data/app/workflows/jumbotron/workflows/execute_discovery_workflow.rb +131 -0
- data/app/workflows/jumbotron/workflows/execute_game_update_policy_workflow.rb +159 -0
- data/app/workflows/jumbotron/workflows/execute_line_update_policy_workflow.rb +156 -0
- data/app/workflows/jumbotron/workflows/public_read.rb +29 -0
- data/app/workflows/jumbotron/workflows/read_consensus_workflow.rb +37 -0
- data/app/workflows/jumbotron/workflows/read_current_lines_workflow.rb +27 -0
- data/app/workflows/jumbotron/workflows/read_game_workflow.rb +34 -0
- data/app/workflows/jumbotron/workflows/read_schedule_groups_workflow.rb +21 -0
- data/app/workflows/jumbotron/workflows/read_schedule_workflow.rb +48 -0
- data/app/workflows/jumbotron/workflows/synchronize_adapter_workflow.rb +183 -0
- data/db/migrate/20260812000001_create_jumbotron_foundational_catalog.rb +68 -0
- data/db/migrate/20260812000002_create_jumbotron_game_graph.rb +70 -0
- data/db/migrate/20260812000003_add_freshness_to_jumbotron_provider_mutated_entities.rb +31 -0
- data/db/migrate/20260812000004_create_jumbotron_observation_evidence.rb +34 -0
- data/db/migrate/20260813000001_create_jumbotron_schedule_groups.rb +24 -0
- data/db/migrate/20260814000001_create_jumbotron_line_observations.rb +38 -0
- data/db/migrate/20260820000001_add_progress_to_jumbotron_games.rb +14 -0
- data/lib/jumbotron/client.rb +40 -0
- data/lib/jumbotron/engine.rb +32 -0
- data/lib/jumbotron/version.rb +5 -0
- data/lib/jumbotron.rb +13 -0
- data/lib/tasks/jumbotron_schedules.rake +12 -0
- metadata +239 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class Season
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :year, type: Support.types.integer, required: true
|
|
10
|
+
field :type, type: Support.types.integer, required: true
|
|
11
|
+
field :slug, type: Support.types.string, nullable: true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.call(payload)
|
|
15
|
+
Support.ensure_hash!(payload, label: "season")
|
|
16
|
+
|
|
17
|
+
Result.build!(
|
|
18
|
+
year: Support.payload.fetch(payload, "year"),
|
|
19
|
+
type: Support.payload.fetch(payload, "type"),
|
|
20
|
+
slug: Support.payload.fetch(payload, "slug")
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class Status
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :clock, type: Support.types.union(Support.types.integer, Support.types.string), nullable: true
|
|
10
|
+
field :display_clock, type: Support.types.string, nullable: true
|
|
11
|
+
field :period, type: Support.types.integer, nullable: true
|
|
12
|
+
field :type_id, type: Support.types.string, nullable: true
|
|
13
|
+
field :type_name, type: Support.types.string, required: true
|
|
14
|
+
field :type_state, type: Support.types.string, required: true
|
|
15
|
+
field :type_completed, type: Support.types.boolean, required: true
|
|
16
|
+
field :type_description, type: Support.types.string, nullable: true
|
|
17
|
+
field :type_detail, type: Support.types.string, nullable: true
|
|
18
|
+
field :type_short_detail, type: Support.types.string, nullable: true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.call(payload)
|
|
22
|
+
Support.ensure_hash!(payload, label: "status")
|
|
23
|
+
type = fetch_type!(payload)
|
|
24
|
+
|
|
25
|
+
Result.build!(
|
|
26
|
+
clock: Support.whole_number(Support.payload.fetch(payload, "clock")),
|
|
27
|
+
display_clock: Support.payload.fetch(payload, "displayClock"),
|
|
28
|
+
period: Support.payload.fetch(payload, "period"),
|
|
29
|
+
type_id: coerce_string(Support.payload.fetch(type, "id")),
|
|
30
|
+
type_name: Support.payload.fetch(type, "name"),
|
|
31
|
+
type_state: Support.payload.fetch(type, "state"),
|
|
32
|
+
type_completed: Support.payload.fetch(type, "completed"),
|
|
33
|
+
type_description: Support.payload.fetch(type, "description"),
|
|
34
|
+
type_detail: Support.payload.fetch(type, "detail"),
|
|
35
|
+
type_short_detail: Support.payload.fetch(type, "shortDetail")
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.fetch_type!(payload)
|
|
40
|
+
raw = Support.payload.fetch(payload, "type")
|
|
41
|
+
if raw.equal?(Support.missing) || raw.nil?
|
|
42
|
+
raise CommandTower::Clients::Errors::DeserializationError.new(
|
|
43
|
+
message: "status.type is required",
|
|
44
|
+
details: {
|
|
45
|
+
path: "type",
|
|
46
|
+
expected: "Hash",
|
|
47
|
+
actual: "missing",
|
|
48
|
+
rule: "required",
|
|
49
|
+
messages: ["status.type is required"]
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
Support.ensure_hash!(raw, label: "status.type")
|
|
55
|
+
raw
|
|
56
|
+
end
|
|
57
|
+
private_class_method :fetch_type!
|
|
58
|
+
|
|
59
|
+
def self.coerce_string(raw)
|
|
60
|
+
return raw if raw.equal?(Support.missing) || raw.nil? || raw.is_a?(String)
|
|
61
|
+
|
|
62
|
+
raw.to_s
|
|
63
|
+
end
|
|
64
|
+
private_class_method :coerce_string
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
module Support
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def types
|
|
11
|
+
CommandTower::Deserializers::Clients::Types
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def payload
|
|
15
|
+
CommandTower::Deserializers::Clients::Payload
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def missing
|
|
19
|
+
CommandTower::Deserializers::Clients::Missing
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def ensure_hash!(value, label:)
|
|
23
|
+
return if value.is_a?(Hash)
|
|
24
|
+
|
|
25
|
+
raise CommandTower::Clients::Errors::DeserializationError.new(
|
|
26
|
+
message: "#{label} payload must be a Hash",
|
|
27
|
+
details: {
|
|
28
|
+
path: "",
|
|
29
|
+
expected: "Hash",
|
|
30
|
+
actual: value.class.name,
|
|
31
|
+
rule: "type",
|
|
32
|
+
messages: ["#{label} payload must be a Hash"]
|
|
33
|
+
}
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def nest(key, raw)
|
|
38
|
+
return raw if raw.equal?(missing) || raw.nil?
|
|
39
|
+
|
|
40
|
+
yield raw
|
|
41
|
+
rescue CommandTower::Clients::Errors::DeserializationError => e
|
|
42
|
+
raise CommandTower::Deserializers::Clients::Errors.prefix(e, key)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def whole_number(raw)
|
|
46
|
+
return raw if raw.equal?(missing) || raw.nil?
|
|
47
|
+
return raw.to_i if raw.is_a?(Float) && raw == raw.to_i
|
|
48
|
+
|
|
49
|
+
raw
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def coerce_id(raw)
|
|
53
|
+
return raw if raw.equal?(missing) || raw.nil? || raw.is_a?(String)
|
|
54
|
+
|
|
55
|
+
raw.to_s
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class Team
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :id, type: Support.types.string, required: true
|
|
10
|
+
field :uid, type: Support.types.string, nullable: true
|
|
11
|
+
field :abbreviation, type: Support.types.string, nullable: true
|
|
12
|
+
field :display_name, type: Support.types.string, required: true
|
|
13
|
+
field :short_display_name, type: Support.types.string, nullable: true
|
|
14
|
+
field :name, type: Support.types.string, nullable: true
|
|
15
|
+
field :location, type: Support.types.string, nullable: true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.call(payload)
|
|
19
|
+
Support.ensure_hash!(payload, label: "team")
|
|
20
|
+
|
|
21
|
+
Result.build!(
|
|
22
|
+
id: coerce_id(Support.payload.fetch(payload, "id")),
|
|
23
|
+
uid: Support.payload.fetch(payload, "uid"),
|
|
24
|
+
abbreviation: Support.payload.fetch(payload, "abbreviation"),
|
|
25
|
+
display_name: Support.payload.fetch(payload, "displayName"),
|
|
26
|
+
short_display_name: Support.payload.fetch(payload, "shortDisplayName"),
|
|
27
|
+
name: Support.payload.fetch(payload, "name"),
|
|
28
|
+
location: Support.payload.fetch(payload, "location")
|
|
29
|
+
)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.coerce_id(raw)
|
|
33
|
+
return raw if raw.equal?(Support.missing) || raw.nil? || raw.is_a?(String)
|
|
34
|
+
|
|
35
|
+
raw.to_s
|
|
36
|
+
end
|
|
37
|
+
private_class_method :coerce_id
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
module Teams
|
|
8
|
+
class Get
|
|
9
|
+
def self.call(payload)
|
|
10
|
+
Support.ensure_hash!(payload, label: "teams")
|
|
11
|
+
|
|
12
|
+
teams_raw = extract_team_entries(payload)
|
|
13
|
+
teams_raw.each_with_index.map do |entry, index|
|
|
14
|
+
team_payload = entry.is_a?(Hash) ? (entry["team"] || entry) : entry
|
|
15
|
+
Team.call(team_payload)
|
|
16
|
+
rescue CommandTower::Clients::Errors::DeserializationError => e
|
|
17
|
+
raise CommandTower::Deserializers::Clients::Errors.prefix(e, "teams[#{index}]")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.extract_team_entries(payload)
|
|
22
|
+
sports = Support.payload.fetch(payload, "sports")
|
|
23
|
+
if sports.equal?(Support.missing) || !sports.is_a?(Array) || sports.empty?
|
|
24
|
+
raise CommandTower::Clients::Errors::DeserializationError.new(
|
|
25
|
+
message: "sports[0] is required",
|
|
26
|
+
details: {
|
|
27
|
+
path: "sports",
|
|
28
|
+
expected: "Array",
|
|
29
|
+
actual: sports.equal?(Support.missing) ? "missing" : sports.class.name,
|
|
30
|
+
rule: "required",
|
|
31
|
+
messages: ["sports[0] is required"]
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
sport = sports.first
|
|
37
|
+
Support.ensure_hash!(sport, label: "sports[0]")
|
|
38
|
+
leagues = Support.payload.fetch(sport, "leagues")
|
|
39
|
+
if leagues.equal?(Support.missing) || !leagues.is_a?(Array) || leagues.empty?
|
|
40
|
+
raise CommandTower::Clients::Errors::DeserializationError.new(
|
|
41
|
+
message: "sports[0].leagues[0] is required",
|
|
42
|
+
details: {
|
|
43
|
+
path: "sports[0].leagues",
|
|
44
|
+
expected: "Array",
|
|
45
|
+
actual: leagues.equal?(Support.missing) ? "missing" : leagues.class.name,
|
|
46
|
+
rule: "required",
|
|
47
|
+
messages: ["sports[0].leagues[0] is required"]
|
|
48
|
+
}
|
|
49
|
+
)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
league = leagues.first
|
|
53
|
+
Support.ensure_hash!(league, label: "sports[0].leagues[0]")
|
|
54
|
+
teams = Support.payload.fetch(league, "teams")
|
|
55
|
+
if teams.equal?(Support.missing) || !teams.is_a?(Array)
|
|
56
|
+
raise CommandTower::Clients::Errors::DeserializationError.new(
|
|
57
|
+
message: "sports[0].leagues[0].teams is required",
|
|
58
|
+
details: {
|
|
59
|
+
path: "sports[0].leagues[0].teams",
|
|
60
|
+
expected: "Array",
|
|
61
|
+
actual: teams.equal?(Support.missing) ? "missing" : teams.class.name,
|
|
62
|
+
rule: "required",
|
|
63
|
+
messages: ["sports[0].leagues[0].teams is required"]
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
teams
|
|
69
|
+
end
|
|
70
|
+
private_class_method :extract_team_entries
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class Venue
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :id, type: Support.types.string, required: true
|
|
10
|
+
field :full_name, type: Support.types.string, required: true
|
|
11
|
+
field :indoor, type: Support.types.boolean, nullable: true
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.call(payload)
|
|
15
|
+
Support.ensure_hash!(payload, label: "venue")
|
|
16
|
+
|
|
17
|
+
Result.build!(
|
|
18
|
+
id: coerce_id(Support.payload.fetch(payload, "id")),
|
|
19
|
+
full_name: Support.payload.fetch(payload, "fullName"),
|
|
20
|
+
indoor: Support.payload.fetch(payload, "indoor")
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.coerce_id(raw)
|
|
25
|
+
return raw if raw.equal?(Support.missing) || raw.nil? || raw.is_a?(String)
|
|
26
|
+
|
|
27
|
+
raw.to_s
|
|
28
|
+
end
|
|
29
|
+
private_class_method :coerce_id
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Clients
|
|
6
|
+
module Espn
|
|
7
|
+
class Week
|
|
8
|
+
class Result < CommandTower::Deserializers::Clients::Result
|
|
9
|
+
field :number, type: Support.types.integer, required: true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.call(payload)
|
|
13
|
+
Support.ensure_hash!(payload, label: "week")
|
|
14
|
+
|
|
15
|
+
Result.build!(
|
|
16
|
+
number: Support.payload.fetch(payload, "number")
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Public
|
|
6
|
+
class Game < CommandTower::Deserializers::ApplicationDeserializer
|
|
7
|
+
def call(params)
|
|
8
|
+
id_raw = unwrap(fetch_param(params, :id))
|
|
9
|
+
return id_raw if deserializer_result?(id_raw)
|
|
10
|
+
return failure(errors: [{ code: "invalid_request", message: "id is required" }]) if id_raw.nil?
|
|
11
|
+
|
|
12
|
+
id = unwrap(require_integer(id_raw, field: "id", min: 1))
|
|
13
|
+
return id if deserializer_result?(id)
|
|
14
|
+
|
|
15
|
+
include_raw = unwrap(fetch_param(params, :include))
|
|
16
|
+
return include_raw if deserializer_result?(include_raw)
|
|
17
|
+
|
|
18
|
+
includes = unwrap(Includes.parse(include_raw))
|
|
19
|
+
return includes if deserializer_result?(includes)
|
|
20
|
+
|
|
21
|
+
success(Jumbotron::Public::GameRequest.new(id: id, includes: includes))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Public
|
|
6
|
+
class GameIds < CommandTower::Deserializers::ApplicationDeserializer
|
|
7
|
+
def call(params)
|
|
8
|
+
raw = unwrap(fetch_param(params, :game_ids))
|
|
9
|
+
return raw if deserializer_result?(raw)
|
|
10
|
+
return failure(errors: [{ code: "invalid_request", message: "game_ids is required" }]) if raw.nil?
|
|
11
|
+
unless raw.is_a?(Array)
|
|
12
|
+
return failure(errors: [{ code: "invalid_request", message: "game_ids must be an array" }])
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
ids = []
|
|
16
|
+
raw.each do |item|
|
|
17
|
+
coerced = unwrap(require_integer(item, field: "game_ids", min: 1))
|
|
18
|
+
return coerced if deserializer_result?(coerced)
|
|
19
|
+
|
|
20
|
+
ids << coerced
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
success(Jumbotron::Public::GameIdsRequest.new(game_ids: ids.uniq))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Public
|
|
6
|
+
module Includes
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def parse(raw)
|
|
10
|
+
return CommandTower::Deserializers::CoercionResult.ok([]) if raw.nil?
|
|
11
|
+
|
|
12
|
+
list = raw.is_a?(Array) ? raw : [raw]
|
|
13
|
+
symbols = list.map { |item| item.is_a?(String) ? item.strip.to_sym : item }
|
|
14
|
+
unless symbols.all? { |item| item.is_a?(Symbol) && item != :"" }
|
|
15
|
+
return CommandTower::Deserializers::CoercionResult.fail(code: "invalid_request", field: "include")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
unknown = symbols - Jumbotron::Public::ALLOWED_INCLUDES
|
|
19
|
+
if unknown.any?
|
|
20
|
+
return CommandTower::Deserializers::CoercionResult.fail(
|
|
21
|
+
code: "invalid_request",
|
|
22
|
+
field: "include",
|
|
23
|
+
details: { unknown: unknown }
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
CommandTower::Deserializers::CoercionResult.ok(symbols.uniq)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Public
|
|
6
|
+
class Schedule < CommandTower::Deserializers::ApplicationDeserializer
|
|
7
|
+
def call(params)
|
|
8
|
+
values = {}
|
|
9
|
+
%i[
|
|
10
|
+
league_id sport league season_id season season_phase schedule_group_id
|
|
11
|
+
group starts_at ends_at include
|
|
12
|
+
].each do |key|
|
|
13
|
+
raw = unwrap(fetch_param(params, key))
|
|
14
|
+
return raw if deserializer_result?(raw)
|
|
15
|
+
|
|
16
|
+
values[key] = raw
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
league_id = optional_positive_integer(values[:league_id], "league_id")
|
|
20
|
+
return league_id if deserializer_result?(league_id)
|
|
21
|
+
|
|
22
|
+
season_id = optional_positive_integer(values[:season_id], "season_id")
|
|
23
|
+
return season_id if deserializer_result?(season_id)
|
|
24
|
+
|
|
25
|
+
schedule_group_id = optional_positive_integer(values[:schedule_group_id], "schedule_group_id")
|
|
26
|
+
return schedule_group_id if deserializer_result?(schedule_group_id)
|
|
27
|
+
|
|
28
|
+
sport = optional_name(values[:sport])
|
|
29
|
+
return sport if deserializer_result?(sport)
|
|
30
|
+
|
|
31
|
+
league = optional_name(values[:league])
|
|
32
|
+
return league if deserializer_result?(league)
|
|
33
|
+
|
|
34
|
+
season = optional_name(values[:season])
|
|
35
|
+
return season if deserializer_result?(season)
|
|
36
|
+
|
|
37
|
+
season_phase = optional_name(values[:season_phase])
|
|
38
|
+
return season_phase if deserializer_result?(season_phase)
|
|
39
|
+
|
|
40
|
+
group = parse_group(values[:group])
|
|
41
|
+
return group if deserializer_result?(group)
|
|
42
|
+
|
|
43
|
+
starts_at = parse_time(values[:starts_at], "starts_at")
|
|
44
|
+
return starts_at if deserializer_result?(starts_at)
|
|
45
|
+
|
|
46
|
+
ends_at = parse_time(values[:ends_at], "ends_at")
|
|
47
|
+
return ends_at if deserializer_result?(ends_at)
|
|
48
|
+
|
|
49
|
+
includes = unwrap(Includes.parse(values[:include]))
|
|
50
|
+
return includes if deserializer_result?(includes)
|
|
51
|
+
|
|
52
|
+
request = Jumbotron::Public::ScheduleRequest.new(
|
|
53
|
+
league_id: league_id,
|
|
54
|
+
sport: sport,
|
|
55
|
+
league: league,
|
|
56
|
+
season_id: season_id,
|
|
57
|
+
season: season,
|
|
58
|
+
season_phase: season_phase,
|
|
59
|
+
schedule_group_id: schedule_group_id,
|
|
60
|
+
group: group,
|
|
61
|
+
starts_at: starts_at,
|
|
62
|
+
ends_at: ends_at,
|
|
63
|
+
includes: includes
|
|
64
|
+
)
|
|
65
|
+
error = validate_modes(request)
|
|
66
|
+
return error if error
|
|
67
|
+
|
|
68
|
+
success(request)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def optional_positive_integer(raw, field)
|
|
74
|
+
return if raw.nil?
|
|
75
|
+
|
|
76
|
+
unwrap(require_integer(raw, field: field, min: 1))
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def optional_name(raw)
|
|
80
|
+
return if raw.nil?
|
|
81
|
+
|
|
82
|
+
unwrap(require_string(raw, field: "name"))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def parse_group(raw)
|
|
86
|
+
return if raw.nil?
|
|
87
|
+
return failure(errors: [{ code: "invalid_request", message: "group must be a hash" }]) unless raw.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
hash = raw.respond_to?(:to_h) ? raw.to_h : raw
|
|
90
|
+
kind_raw = hash[:kind] || hash["kind"]
|
|
91
|
+
number_raw = hash[:number] || hash["number"]
|
|
92
|
+
kind = unwrap(require_string(kind_raw, field: "group.kind"))
|
|
93
|
+
return kind if deserializer_result?(kind)
|
|
94
|
+
|
|
95
|
+
number = unwrap(require_integer(number_raw, field: "group.number", min: 1))
|
|
96
|
+
return number if deserializer_result?(number)
|
|
97
|
+
|
|
98
|
+
{ kind: kind, number: number }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def parse_time(raw, field)
|
|
102
|
+
return if raw.nil?
|
|
103
|
+
return raw if raw.is_a?(Time) || raw.is_a?(ActiveSupport::TimeWithZone)
|
|
104
|
+
|
|
105
|
+
failure(errors: [{ code: "invalid_request", message: "#{field} must be a Time" }])
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def validate_modes(request)
|
|
109
|
+
has_league = request.league_id.present? || (request.sport.present? && request.league.present?)
|
|
110
|
+
unless has_league
|
|
111
|
+
return failure(
|
|
112
|
+
errors: [{ code: "invalid_request", message: "league_id or sport+league is required" }]
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
has_group = request.schedule_group_id.present? || request.group.present?
|
|
117
|
+
has_time = request.starts_at.present? && request.ends_at.present?
|
|
118
|
+
if has_group && has_time
|
|
119
|
+
return failure(
|
|
120
|
+
errors: [{ code: "invalid_request", message: "provide group or time range, not both" }]
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
if !has_group && !has_time
|
|
124
|
+
return failure(
|
|
125
|
+
errors: [{ code: "invalid_request", message: "provide group or starts_at/ends_at" }]
|
|
126
|
+
)
|
|
127
|
+
end
|
|
128
|
+
if has_time && (request.starts_at.blank? || request.ends_at.blank?)
|
|
129
|
+
return failure(
|
|
130
|
+
errors: [{ code: "invalid_request", message: "starts_at and ends_at are required together" }]
|
|
131
|
+
)
|
|
132
|
+
end
|
|
133
|
+
if has_group && request.schedule_group_id.blank?
|
|
134
|
+
unless request.season_id.present? || request.season.present?
|
|
135
|
+
return failure(
|
|
136
|
+
errors: [{ code: "invalid_request", message: "season_id or season is required for group queries" }]
|
|
137
|
+
)
|
|
138
|
+
end
|
|
139
|
+
if request.season_phase.blank?
|
|
140
|
+
return failure(
|
|
141
|
+
errors: [{ code: "invalid_request", message: "season_phase is required for group queries" }]
|
|
142
|
+
)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jumbotron
|
|
4
|
+
module Deserializers
|
|
5
|
+
module Public
|
|
6
|
+
class ScheduleGroups < CommandTower::Deserializers::ApplicationDeserializer
|
|
7
|
+
def call(params)
|
|
8
|
+
values = {}
|
|
9
|
+
%i[league_id sport league season_id season season_phase kind].each do |key|
|
|
10
|
+
raw = unwrap(fetch_param(params, key))
|
|
11
|
+
return raw if deserializer_result?(raw)
|
|
12
|
+
|
|
13
|
+
values[key] = raw
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
league_id = optional_positive_integer(values[:league_id], "league_id")
|
|
17
|
+
return league_id if deserializer_result?(league_id)
|
|
18
|
+
|
|
19
|
+
season_id = optional_positive_integer(values[:season_id], "season_id")
|
|
20
|
+
return season_id if deserializer_result?(season_id)
|
|
21
|
+
|
|
22
|
+
sport = optional_name(values[:sport])
|
|
23
|
+
return sport if deserializer_result?(sport)
|
|
24
|
+
|
|
25
|
+
league = optional_name(values[:league])
|
|
26
|
+
return league if deserializer_result?(league)
|
|
27
|
+
|
|
28
|
+
season = optional_name(values[:season])
|
|
29
|
+
return season if deserializer_result?(season)
|
|
30
|
+
|
|
31
|
+
season_phase = optional_name(values[:season_phase])
|
|
32
|
+
return season_phase if deserializer_result?(season_phase)
|
|
33
|
+
|
|
34
|
+
kind = optional_name(values[:kind])
|
|
35
|
+
return kind if deserializer_result?(kind)
|
|
36
|
+
|
|
37
|
+
request = Jumbotron::Public::ScheduleGroupsRequest.new(
|
|
38
|
+
league_id: league_id,
|
|
39
|
+
sport: sport,
|
|
40
|
+
league: league,
|
|
41
|
+
season_id: season_id,
|
|
42
|
+
season: season,
|
|
43
|
+
season_phase: season_phase,
|
|
44
|
+
kind: kind
|
|
45
|
+
)
|
|
46
|
+
error = validate_modes(request)
|
|
47
|
+
return error if error
|
|
48
|
+
|
|
49
|
+
success(request)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def optional_positive_integer(raw, field)
|
|
55
|
+
return if raw.nil?
|
|
56
|
+
|
|
57
|
+
unwrap(require_integer(raw, field: field, min: 1))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def optional_name(raw)
|
|
61
|
+
return if raw.nil?
|
|
62
|
+
|
|
63
|
+
unwrap(require_string(raw, field: "name"))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def validate_modes(request)
|
|
67
|
+
has_league = request.league_id.present? || (request.sport.present? && request.league.present?)
|
|
68
|
+
unless has_league
|
|
69
|
+
return failure(
|
|
70
|
+
errors: [{ code: "invalid_request", message: "league_id or sport+league is required" }]
|
|
71
|
+
)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
unless request.season_id.present? || request.season.present?
|
|
75
|
+
return failure(
|
|
76
|
+
errors: [{ code: "invalid_request", message: "season_id or season is required" }]
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if request.season_phase.blank?
|
|
81
|
+
return failure(
|
|
82
|
+
errors: [{ code: "invalid_request", message: "season_phase is required" }]
|
|
83
|
+
)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
nil
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|