Introduce architettura multi-sport con catalogo, engine scoring e overlay.

Catalogo sport in YAML, board implicito, engine per volley/basket/timed/racket/timer/generic, regia e app Android allineate.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-09 20:11:21 +02:00
parent 53f9a6a884
commit e727069d43
56 changed files with 1783 additions and 223 deletions

View File

@@ -16,7 +16,10 @@ module Api
end
def create
match = @team.matches.create!(match_params)
attrs = match_params.to_h
attrs["sport_key"] = @team.sport_key if attrs["sport_key"].blank?
normalize_scoring_rules!(attrs)
match = @team.matches.create!(attrs)
attach_opponent_logo(match)
render json: match_json(match), status: :created
end
@@ -59,11 +62,15 @@ module Api
end
def match_params
params.require(:match).permit(
:opponent_name, :location, :scheduled_at, :sport, :sets_to_win,
:category, :opponent_primary_color,
scoring_rules: %i[points_per_set points_deciding_set min_point_lead]
p = params.require(:match).permit(
:opponent_name, :location, :scheduled_at, :sport, :sport_key, :sets_to_win,
:category, :opponent_primary_color, :overlay_kind,
scoring_rules: {}
)
if p[:sport].present? && p[:sport_key].blank?
p[:sport_key] = p.delete(:sport)
end
p
end
def normalize_scoring_rules!(attrs)
@@ -71,6 +78,9 @@ module Api
rules = attrs["scoring_rules"]
attrs["scoring_rules"] = {} if rules.blank?
if attrs["sets_to_win"].present? && attrs["scoring_rules"].is_a?(Hash)
attrs["scoring_rules"]["sets_to_win"] ||= attrs["sets_to_win"]
end
end
def normalize_opponent_color!(attrs)
@@ -97,9 +107,15 @@ module Api
opponent_name: match.opponent_name,
location: match.location,
scheduled_at: match.scheduled_at,
sport: match.sport,
sport: match.sport_key,
sport_key: match.sport_key,
sport_label: match.sport_label,
board_type: match.effective_board_type,
overlay_kind: match.overlay_kind,
effective_overlay_kind: match.effective_overlay_kind,
sets_to_win: match.sets_to_win,
scoring_rules: match.scoring_rules.presence,
effective_scoring_rules: match.effective_scoring_rules,
category: match.category,
home_primary_color: team.effective_primary_color,
home_secondary_color: team.effective_secondary_color,

View File

@@ -0,0 +1,9 @@
module Api
module V1
class SportsController < BaseController
def index
render json: Sports::Catalog.as_api_list
end
end
end
end

View File

@@ -21,10 +21,11 @@ module Api
}, status: :unprocessable_entity
end
club = Club.create!(name: team_params[:name], sport: team_params[:sport] || "volleyball",
sport_key = Sports::Catalog.normalize_key(team_params[:sport_key] || team_params[:sport] || "pallavolo")
club = Club.create!(name: team_params[:name], sport: sport_key,
primary_color: "#e53935", secondary_color: "#ffffff")
ClubMembership.create!(user: current_user, club: club, role: "owner")
team = club.teams.create!(name: team_params[:name], sport: club.sport)
team = club.teams.create!(name: team_params[:name], sport_key: sport_key)
Billing::AssignPlan.call(club: club, plan_slug: "free")
render json: team_json(team), status: :created
end
@@ -74,7 +75,11 @@ module Api
private
def team_params
p = params.require(:team).permit(:name, :sport, :logo_url, :primary_color, :secondary_color)
p = params.require(:team).permit(:name, :sport, :sport_key, :logo_url, :primary_color, :secondary_color)
if p[:sport].present? && p[:sport_key].blank?
p[:sport_key] = p.delete(:sport)
end
p
if p[:primary_color].present?
p[:primary_color] = normalize_hex_color(p[:primary_color], p[:primary_color])
end
@@ -96,7 +101,10 @@ module Api
data = {
id: team.id,
name: team.name,
sport: team.sport,
sport: team.sport_key,
sport_key: team.sport_key,
sport_label: team.sport_label,
board_type: team.effective_board_type,
logo_url: api_absolute_url(team.effective_logo_url),
primary_color: team.effective_primary_color,
secondary_color: team.effective_secondary_color,