Limita una diretta attiva per account e valorizza la copertina Premium Full.
Blocca la seconda diretta sullo stesso utente, registra gli abusi in admin e presenta la copertina come grafica caricata dalla società, con demo Team MLTV. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class StreamConcurrencyViolation < ApplicationRecord
|
||||
LOOKBACK = 30.days
|
||||
|
||||
belongs_to :user, optional: true
|
||||
belongs_to :occupying_session, class_name: "StreamSession", optional: true
|
||||
belongs_to :attempted_session, class_name: "StreamSession", optional: true
|
||||
belongs_to :occupying_club, class_name: "Club", optional: true
|
||||
belongs_to :attempted_club, class_name: "Club", optional: true
|
||||
|
||||
validates :user_email, :occupying_match_label, :attempted_match_label, presence: true
|
||||
validates :attempt_action, inclusion: { in: %w[start resume] }
|
||||
|
||||
scope :recent, -> { order(created_at: :desc) }
|
||||
scope :since, ->(time) { where("created_at >= ?", time) }
|
||||
scope :lookback, -> { since(LOOKBACK.ago) }
|
||||
scope :two_devices, -> { where(devices_differ: true) }
|
||||
scope :for_club, lambda { |club_id|
|
||||
where("occupying_club_id = :id OR attempted_club_id = :id", id: club_id)
|
||||
}
|
||||
scope :for_session, lambda { |session_id|
|
||||
where("occupying_session_id = :id OR attempted_session_id = :id", id: session_id)
|
||||
}
|
||||
|
||||
def self.record!(attempted:, occupying:, action: "start")
|
||||
user = attempted.user || occupying.user
|
||||
occupying_club = occupying.match&.team&.club
|
||||
attempted_club = attempted.match&.team&.club
|
||||
occupying_device = occupying.client_device_label
|
||||
attempted_device = attempted.client_device_label
|
||||
|
||||
create!(
|
||||
user: user,
|
||||
occupying_session: occupying,
|
||||
attempted_session: attempted,
|
||||
occupying_club: occupying_club,
|
||||
attempted_club: attempted_club,
|
||||
attempt_action: action.to_s,
|
||||
user_email: user&.email.presence || "unknown",
|
||||
user_name: user&.name,
|
||||
occupying_club_name: occupying_club&.name,
|
||||
attempted_club_name: attempted_club&.name,
|
||||
occupying_match_label: occupying.match_label,
|
||||
attempted_match_label: attempted.match_label,
|
||||
occupying_status: occupying.status,
|
||||
occupying_device: occupying_device,
|
||||
attempted_device: attempted_device,
|
||||
devices_differ: StreamSession.devices_differ?(occupying, attempted),
|
||||
metadata: {
|
||||
occupying_session_id: occupying.id,
|
||||
attempted_session_id: attempted.id,
|
||||
occupying_client_os: occupying.client_os,
|
||||
attempted_client_os: attempted.client_os,
|
||||
occupying_app_version: occupying.app_version,
|
||||
attempted_app_version: attempted.app_version
|
||||
}.compact
|
||||
)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[StreamConcurrencyViolation] record failed: #{e.class} #{e.message}")
|
||||
nil
|
||||
end
|
||||
|
||||
def club_name
|
||||
attempted_club_name.presence || occupying_club_name
|
||||
end
|
||||
|
||||
def operator_label
|
||||
[user_name.presence, user_email].compact.join(" · ")
|
||||
end
|
||||
end
|
||||
@@ -10,6 +10,10 @@ class StreamSession < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :stream_node, optional: true
|
||||
has_many :stream_events, dependent: :destroy
|
||||
has_many :occupying_concurrency_violations, class_name: "StreamConcurrencyViolation",
|
||||
foreign_key: :occupying_session_id, dependent: :nullify, inverse_of: :occupying_session
|
||||
has_many :attempted_concurrency_violations, class_name: "StreamConcurrencyViolation",
|
||||
foreign_key: :attempted_session_id, dependent: :nullify, inverse_of: :attempted_session
|
||||
has_one :score_state, dependent: :destroy
|
||||
has_one :recording
|
||||
has_many :device_states, dependent: :destroy
|
||||
@@ -84,6 +88,35 @@ class StreamSession < ApplicationRecord
|
||||
stream_node&.role.presence || ingest_role
|
||||
end
|
||||
|
||||
def match_label
|
||||
team_name = match&.team&.name
|
||||
opponent = match&.opponent_name
|
||||
return id.to_s if team_name.blank?
|
||||
|
||||
opponent.present? ? "#{team_name} vs #{opponent}" : team_name
|
||||
end
|
||||
|
||||
def client_device_label
|
||||
parts = []
|
||||
parts << client_os if client_os.present?
|
||||
device = [device_manufacturer, device_model].compact_blank.join(" ")
|
||||
parts << device if device.present?
|
||||
parts << "OS #{os_version}" if os_version.present?
|
||||
parts.join(" · ").presence
|
||||
end
|
||||
|
||||
def client_device_key
|
||||
[client_os, device_manufacturer, device_model].map { |v| v.to_s.strip.downcase }.join("|")
|
||||
end
|
||||
|
||||
def self.devices_differ?(left, right)
|
||||
ka = left.client_device_key
|
||||
kb = right.client_device_key
|
||||
return false if ka.delete("|").blank? || kb.delete("|").blank?
|
||||
|
||||
ka != kb
|
||||
end
|
||||
|
||||
def rtmp_ingest_url
|
||||
# RootEncoder richiede rtmp://host:port/app/stream (due segmenti).
|
||||
# MediaMTX path = live/match_{uuid} (no ?token= nel path).
|
||||
|
||||
@@ -11,6 +11,7 @@ class User < ApplicationRecord
|
||||
has_many :clubs, through: :club_memberships
|
||||
has_many :owned_clubs, -> { where(club_memberships: { role: "owner" }) }, through: :club_memberships, source: :club
|
||||
has_many :stream_sessions, dependent: :nullify
|
||||
has_many :stream_concurrency_violations, dependent: :nullify
|
||||
|
||||
def manageable_teams
|
||||
staff_ids = teams.select(:id)
|
||||
|
||||
Reference in New Issue
Block a user