Aggiunge modulo Replay, Garage dev e YouTube a livello società.

Pipeline registrazione/upload con storage S3, archivio web e app, proxy replay
per il browser, OAuth YouTube sulla pagina club (Premium Full) e footer legale.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-03 07:53:11 +02:00
parent a87cda156b
commit 1f273f849d
87 changed files with 2952 additions and 195 deletions

View File

@@ -0,0 +1,35 @@
require "rails_helper"
RSpec.describe Recordings::AccessPolicy do
let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
let!(:team) { club.teams.create!(name: "Team", sport: "volleyball") }
let!(:user) { User.create!(email: "coach@test.com", name: "Coach", password: "password123", role: "coach") }
let!(:match) { team.matches.create!(opponent_name: "Rival", scheduled_at: 1.day.from_now) }
let!(:session) do
StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "ended")
end
let!(:recording) do
Recording.create!(
stream_session: session,
team: team,
status: "ready",
privacy_status: "unlisted",
expires_at: 10.days.from_now,
storage_key: "key"
)
end
it "allows unlisted replay without login" do
expect(described_class.new(recording, viewer: nil).allowed?).to be true
end
it "allows public replay without login" do
recording.update!(privacy_status: "public")
expect(described_class.new(recording, viewer: nil).allowed?).to be true
end
it "denies expired replay" do
recording.update!(expires_at: 1.day.ago)
expect(described_class.new(recording, viewer: nil).allowed?).to be false
end
end

View File

@@ -0,0 +1,26 @@
require "rails_helper"
RSpec.describe Recordings::ClubStats do
let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
let!(:team) { club.teams.create!(name: "Team", sport: "volleyball") }
let!(:user) { User.create!(email: "coach@test.com", name: "Coach", password: "password123", role: "coach") }
let!(:match) { team.matches.create!(opponent_name: "Rival", scheduled_at: 1.day.from_now) }
let!(:session) do
StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "ended")
end
it "aggregates ready replays" do
Recording.create!(
stream_session: session,
team: team,
status: "ready",
byte_size: 100.megabytes,
expires_at: 3.days.from_now,
storage_key: "a"
)
stats = described_class.new(club).call
expect(stats[:available_count]).to eq(1)
expect(stats[:expiring_soon_count]).to eq(1)
expect(stats[:total_bytes]).to eq(100.megabytes)
end
end

View File

@@ -0,0 +1,35 @@
require "rails_helper"
RSpec.describe Recordings::FinalizeSession do
let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
let!(:team) { club.teams.create!(name: "Team", sport: "volleyball") }
let!(:user) { User.create!(email: "coach@test.com", name: "Coach", password: "password123", role: "coach") }
let!(:match) { team.matches.create!(opponent_name: "Rival", scheduled_at: 1.day.from_now) }
let!(:session) do
StreamSession.create!(
match: match,
user: user,
platform: "matchlivetv",
status: "ended",
privacy_status: "public",
ended_at: Time.current
)
end
before do
load Rails.root.join("db/seeds/plans.rb")
Billing::AssignPlan.call(club: club, plan_slug: "premium_light")
end
it "creates processing recording with retention" do
rec = described_class.new(session).call
expect(rec.status).to eq("processing")
expect(rec.privacy_status).to eq("public")
expect(rec.expires_at).to be > 29.days.from_now
end
it "skips free plan" do
Billing::AssignPlan.call(club: club, plan_slug: "free")
expect(described_class.new(session).call).to be_nil
end
end

View File

@@ -0,0 +1,24 @@
require "rails_helper"
RSpec.describe Recordings::IncrementViews do
let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
let!(:team) { club.teams.create!(name: "Team", sport: "volleyball") }
let!(:user) { User.create!(email: "c@test.com", name: "C", password: "password123", role: "coach") }
let!(:match) { team.matches.create!(opponent_name: "Rival", scheduled_at: 1.day.from_now) }
let!(:session) { StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "ended") }
let!(:recording) do
Recording.create!(
stream_session: session,
team: team,
status: "ready",
expires_at: 10.days.from_now,
storage_key: "key",
view_count: 2
)
end
it "increments view_count" do
described_class.new(recording).call
expect(recording.reload.view_count).to eq(3)
end
end

View File

@@ -0,0 +1,30 @@
require "rails_helper"
RSpec.describe Recordings::NotifyReady do
let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") }
let!(:owner) { User.create!(email: "owner@test.com", name: "Owner", password: "password123", role: "coach") }
let!(:team) { club.teams.create!(name: "Team", sport: "volleyball") }
let!(:user) { User.create!(email: "c@test.com", name: "C", password: "password123", role: "coach") }
let!(:match) { team.matches.create!(opponent_name: "Rival", scheduled_at: 1.day.from_now) }
let!(:session) { StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "ended") }
before do
ClubMembership.create!(club: club, user: owner, role: "owner")
end
it "sends email and marks notified" do
recording = Recording.create!(
stream_session: session,
team: team,
status: "ready",
expires_at: 10.days.from_now,
storage_key: "key"
)
expect {
described_class.new(recording).call
}.to change { ActionMailer::Base.deliveries.size }.by(1)
expect(recording.reload.ready_notified_at).to be_present
end
end