Initial commit: monorepo Match Live TV.

Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 17:45:37 +02:00
commit bba6df52c0
381 changed files with 20599 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
module ApplicationCable
class Channel < ActionCable::Channel::Base
end
end

View File

@@ -0,0 +1,17 @@
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
payload = JsonWebToken.decode(token_from_header)
self.current_user = User.find_by(id: payload[:user_id]) if payload
reject_unauthorized_connection unless current_user
end
private
def token_from_header
request.headers["Authorization"].to_s.delete_prefix("Bearer ").presence
end
end
end

View File

@@ -0,0 +1,9 @@
module CableBroadcastable
extend ActiveSupport::Concern
class_methods do
def broadcast_message(session, data)
broadcast_to(session, method: "receive_message", data: data)
end
end
end

View File

@@ -0,0 +1,83 @@
class SessionChannel < ApplicationCable::Channel
include CableBroadcastable
def subscribed
session = StreamSession.joins(match: :team)
.merge(current_user.teams)
.find(params[:session_id])
stream_for session
session.device_states.find_or_create_by!(device_role: params[:device_role] || "controller")
end
def unsubscribed
stop_all_streams
end
def receive(data)
session = StreamSession.find(params[:session_id])
handle_message(session, data)
end
private
def handle_message(session, data)
case data["type"]
when "command"
SessionChannel.broadcast_message(session, data)
execute_command(session, data)
when "score_update"
update_score(session, data)
when "timeout"
update_timeout(session, data)
when "device_state"
update_device(session, data)
end
end
def execute_command(session, data)
case data["action"]
when "start_stream"
Sessions::Start.new(session).call
when "stop_stream"
Sessions::Stop.new(session).call
when "pause_stream"
Sessions::Pause.new(session).call
end
end
def update_score(session, data)
score = session.score_state || session.create_score_state!
score.update!(
home_sets: data["home_sets"] || score.home_sets,
away_sets: data["away_sets"] || score.away_sets,
home_points: data["home_points"] || score.home_points,
away_points: data["away_points"] || score.away_points,
current_set: data["current_set"] || score.current_set,
set_partials: data.key?("set_partials") ? data["set_partials"] : score.set_partials
)
SessionChannel.broadcast_message(session, score.as_cable_payload)
end
def update_timeout(session, data)
score = session.score_state
return unless score
if data["team"] == "home"
score.update!(timeout_home: true)
else
score.update!(timeout_away: true)
end
SessionChannel.broadcast_message(session, { type: "timeout", team: data["team"] })
end
def update_device(session, data)
state = session.device_states.find_or_initialize_by(device_role: data["device_role"] || "camera")
state.update!(
battery_level: data["battery"],
network_type: data["network"],
current_bitrate: data["bitrate"],
fps: data["fps"],
last_seen_at: Time.current
)
SessionChannel.broadcast_message(session, state.as_cable_payload)
end
end