Club come entità principale, branding e fix infrastruttura streaming.

Introduce clubs con abbonamento, branding Active Storage e flusso registrazione società; corregge healthcheck MediaMTX (immagine distroless) e allinea dominio produzione matchlivetv.it.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-27 09:16:24 +02:00
parent 471291b2c4
commit 4083bc5dee
53 changed files with 1120 additions and 197 deletions

View File

@@ -0,0 +1,27 @@
class Club < ApplicationRecord
include Brandable
has_many :club_memberships, dependent: :destroy
has_many :users, through: :club_memberships
has_many :teams, dependent: :destroy
has_one :subscription, dependent: :destroy
validates :name, presence: true
validates :sport, presence: true
validates :primary_color, presence: true
validates :secondary_color, presence: true
def owner
club_memberships.find_by(role: "owner")&.user
end
def owned_by?(user)
club_memberships.exists?(user: user, role: "owner")
end
private
def branding_parent
nil
end
end

View File

@@ -0,0 +1,9 @@
class ClubMembership < ApplicationRecord
ROLES = %w[owner].freeze
belongs_to :user
belongs_to :club
validates :role, inclusion: { in: ROLES }
validates :user_id, uniqueness: { scope: :club_id }
end

View File

@@ -0,0 +1,43 @@
module Brandable
extend ActiveSupport::Concern
HEX_COLOR = /\A#[0-9A-Fa-f]{6}\z/
included do
has_one_attached :logo_file
validates :primary_color, format: { with: HEX_COLOR }, allow_blank: true
validates :secondary_color, format: { with: HEX_COLOR }, allow_blank: true
end
def effective_primary_color
primary_color.presence || default_primary_color
end
def effective_secondary_color
secondary_color.presence || default_secondary_color
end
def effective_logo_url
if logo_file.attached?
Rails.application.routes.url_helpers.rails_blob_path(logo_file, only_path: true)
elsif logo_url.present?
logo_url
else
branding_parent&.effective_logo_url
end
end
private
def default_primary_color
"#e53935"
end
def default_secondary_color
"#ffffff"
end
def branding_parent
nil
end
end

View File

@@ -2,11 +2,11 @@ class Subscription < ApplicationRecord
STATUSES = %w[active trialing past_due canceled incomplete].freeze
ACTIVE_STATUSES = %w[active trialing].freeze
belongs_to :team
belongs_to :club
belongs_to :plan
validates :status, inclusion: { in: STATUSES }
validates :team_id, uniqueness: true
validates :club_id, uniqueness: true
scope :active, -> { where(status: ACTIVE_STATUSES) }

View File

@@ -1,19 +1,39 @@
class Team < ApplicationRecord
include Brandable
belongs_to :club
has_many :user_teams, dependent: :destroy
has_many :users, through: :user_teams
has_many :matches, dependent: :destroy
has_one :youtube_credential, dependent: :destroy
has_one :subscription, dependent: :destroy
has_many :recordings, dependent: :destroy
has_many :team_invitations, dependent: :destroy
validates :name, presence: true
def subscription
club.subscription
end
def entitlements
@entitlements ||= Teams::Entitlements.new(self)
Teams::Entitlements.new(self)
end
def owner
user_teams.find_by(role: "owner")&.user
club&.owner
end
private
def branding_parent
club
end
def default_primary_color
club&.primary_color.presence || super
end
def default_secondary_color
club&.secondary_color.presence || super
end
end

View File

@@ -5,8 +5,21 @@ class User < ApplicationRecord
has_many :user_teams, dependent: :destroy
has_many :teams, through: :user_teams
has_many :club_memberships, dependent: :destroy
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
def manageable_teams
staff_ids = teams.select(:id)
owner_ids = Team.where(club_id: owned_clubs.select(:id)).select(:id)
Team.where(id: staff_ids).or(Team.where(id: owner_ids))
end
def primary_club
owned_clubs.order(:created_at).first
end
validates :email, presence: true, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
validates :name, presence: true
validates :role, inclusion: { in: ROLES }