diff --git a/backend/app/controllers/admin/base_controller.rb b/backend/app/controllers/admin/base_controller.rb index 3dafc49..0d63075 100644 --- a/backend/app/controllers/admin/base_controller.rb +++ b/backend/app/controllers/admin/base_controller.rb @@ -6,6 +6,8 @@ module Admin before_action :require_admin_login include ::AdminHelper + helper ApplicationHelper + helper RecordingsArchiveHelper helper_method :current_admin_account, :admin_logged_in? private diff --git a/backend/app/controllers/admin/club_recordings_controller.rb b/backend/app/controllers/admin/club_recordings_controller.rb new file mode 100644 index 0000000..5c2b76f --- /dev/null +++ b/backend/app/controllers/admin/club_recordings_controller.rb @@ -0,0 +1,15 @@ +module Admin + class ClubRecordingsController < BaseController + include Recordings::ClubArchiveActions + + private + + def archive_namespace + :admin + end + + def authorize_recording_management! + # Admin può gestire tutti i replay della società senza vincoli di ruolo utente. + end + end +end diff --git a/backend/app/controllers/concerns/recordings/club_archive_actions.rb b/backend/app/controllers/concerns/recordings/club_archive_actions.rb new file mode 100644 index 0000000..fddd316 --- /dev/null +++ b/backend/app/controllers/concerns/recordings/club_archive_actions.rb @@ -0,0 +1,91 @@ +module Recordings + module ClubArchiveActions + extend ActiveSupport::Concern + include RecordingsArchiveHelper + + included do + before_action :set_club + before_action :set_recording, only: %i[update destroy publish_youtube] + end + + def index + load_archive + end + + def update + permitted = params.require(:recording).permit(:privacy_status, :title) + attrs = {} + privacy = permitted[:privacy_status] + attrs[:privacy_status] = privacy if privacy.in?(Recording::PRIVACY_STATUSES) + title = permitted[:title] + attrs[:title] = title if title.present? + privacy_changed = attrs.key?(:privacy_status) && attrs[:privacy_status] != @recording.privacy_status + @recording.update!(attrs) + Recordings::SyncYoutubePrivacyJob.perform_async(@recording.id) if privacy_changed && @recording.youtube_video_id.present? + redirect_to archive_index_path, notice: "Replay aggiornato" + rescue ActiveRecord::RecordInvalid => e + redirect_to archive_index_path, alert: e.record.errors.full_messages.join(", ") + end + + def destroy + Recordings::Delete.new(@recording).call + redirect_to archive_index_path, notice: "Replay eliminato" + end + + def publish_youtube + ent = @recording.team.entitlements + unless ent.premium_full? && ent.youtube_enabled? + redirect_to archive_index_path, alert: "Republicazione YouTube con Premium Full" + return + end + + Recordings::PublishToYoutubeJob.perform_async(@recording.id) + redirect_to archive_index_path, notice: "Pubblicazione su YouTube avviata" + end + + private + + def load_archive + archive = Recordings::ClubArchive.new(@club, params: params) + @stats = archive.stats + @recordings = archive.recordings + @teams = archive.teams + @filter_team_id = archive.filter_team_id + @filter_status = archive.filter_status + @archive_paths = recordings_archive_paths(@club, namespace: archive_namespace) + @archive_filter_params = archive.filter_params + end + + def archive_index_path + archive_paths.index + archive_query_string + end + + def archive_query_string + q = @archive_filter_params.presence + return "" if q.blank? + + "?#{q.to_query}" + end + + def archive_paths + @archive_paths ||= recordings_archive_paths(@club, namespace: archive_namespace) + end + + def set_club + @club = Club.find(params[:club_id]) + end + + def set_recording + @recording = Recording.for_club(@club).not_deleted.find(params[:id]) + authorize_recording_management! + end + + def authorize_recording_management! + # overridden in Public::ClubRecordingsController + end + + def archive_namespace + raise NotImplementedError + end + end +end diff --git a/backend/app/controllers/public/club_recordings_controller.rb b/backend/app/controllers/public/club_recordings_controller.rb index 3d4f770..2a804f6 100644 --- a/backend/app/controllers/public/club_recordings_controller.rb +++ b/backend/app/controllers/public/club_recordings_controller.rb @@ -1,73 +1,21 @@ module Public class ClubRecordingsController < WebBaseController + include Recordings::ClubArchiveActions + helper RecordingsArchiveHelper + before_action :require_login! - before_action :set_club before_action :require_club_recording_manager! - before_action :set_recording, only: %i[update destroy publish_youtube] - - def index - @stats = Recordings::ClubStats.new(@club).call - @recordings = Recording.for_club(@club) - .not_deleted - .includes(stream_session: { match: :team }) - .order(recorded_at: :desc, created_at: :desc) - if params[:team_id].present? - @recordings = @recordings.where(team_id: params[:team_id]) - end - if params[:status].present? - case params[:status] - when "ready" - @recordings = @recordings.ready - when "processing" - @recordings = @recordings.where(status: "processing") - when "expired" - @recordings = @recordings.where( - "recordings.status IN (?) OR (recordings.expires_at IS NOT NULL AND recordings.expires_at <= ?)", - %w[expired failed], Time.current - ) - end - end - @teams = @club.teams.order(:name) - @filter_team_id = params[:team_id] - @filter_status = params[:status] - end - - def update - permitted = params.require(:recording).permit(:privacy_status, :title) - attrs = {} - privacy = permitted[:privacy_status] - attrs[:privacy_status] = privacy if privacy.in?(Recording::PRIVACY_STATUSES) - title = permitted[:title] - attrs[:title] = title if title.present? - privacy_changed = attrs.key?(:privacy_status) && attrs[:privacy_status] != @recording.privacy_status - @recording.update!(attrs) - Recordings::SyncYoutubePrivacyJob.perform_async(@recording.id) if privacy_changed && @recording.youtube_video_id.present? - redirect_to public_club_recordings_path(@club, team_id: params[:team_id], status: params[:status]), - notice: "Replay aggiornato" - rescue ActiveRecord::RecordInvalid => e - redirect_to public_club_recordings_path(@club), alert: e.record.errors.full_messages.join(", ") - end - - def destroy - Recordings::Delete.new(@recording).call - redirect_to public_club_recordings_path(@club), notice: "Replay eliminato" - end - - def publish_youtube - ent = @recording.team.entitlements - unless ent.premium_full? && ent.youtube_enabled? - redirect_to public_club_recordings_path(@club), alert: "Republicazione YouTube con Premium Full" - return - end - - Recordings::PublishToYoutubeJob.perform_async(@recording.id) - redirect_to public_club_recordings_path(@club), notice: "Pubblicazione su YouTube avviata" - end private - def set_club - @club = Club.find(params[:club_id]) + def archive_namespace + :public + end + + def authorize_recording_management! + return if Recordings::AccessPolicy.new(@recording, viewer: current_user).manage_allowed? + + raise ActiveRecord::RecordNotFound end def require_club_recording_manager! @@ -76,12 +24,5 @@ module Public redirect_to public_clubs_path, alert: "Non autorizzato" end - - def set_recording - @recording = Recording.for_club(@club).not_deleted.find(params[:id]) - return if Recordings::AccessPolicy.new(@recording, viewer: current_user).manage_allowed? - - raise ActiveRecord::RecordNotFound - end end end diff --git a/backend/app/helpers/recordings_archive_helper.rb b/backend/app/helpers/recordings_archive_helper.rb new file mode 100644 index 0000000..ebdabf6 --- /dev/null +++ b/backend/app/helpers/recordings_archive_helper.rb @@ -0,0 +1,24 @@ +module RecordingsArchiveHelper + ArchivePaths = Struct.new(:index, :update, :destroy, :publish_youtube, :back, keyword_init: true) + + def recordings_archive_paths(club, namespace:) + case namespace + when :admin + ArchivePaths.new( + index: admin_club_recordings_path(club), + update: ->(rec) { admin_club_recording_path(club, rec) }, + destroy: ->(rec) { admin_club_recording_path(club, rec) }, + publish_youtube: ->(rec) { publish_youtube_admin_club_recording_path(club, rec) }, + back: admin_club_path(club) + ) + else + ArchivePaths.new( + index: public_club_recordings_path(club), + update: ->(rec) { public_club_recording_path(club, rec) }, + destroy: ->(rec) { public_club_recording_path(club, rec) }, + publish_youtube: ->(rec) { public_club_recording_publish_youtube_path(club, rec) }, + back: public_club_path(club) + ) + end + end +end diff --git a/backend/app/services/recordings/club_archive.rb b/backend/app/services/recordings/club_archive.rb new file mode 100644 index 0000000..5782dcf --- /dev/null +++ b/backend/app/services/recordings/club_archive.rb @@ -0,0 +1,55 @@ +module Recordings + class ClubArchive + def initialize(club, params: {}) + @club = club + @params = params + end + + def stats + Recordings::ClubStats.new(@club).call + end + + def teams + @club.teams.order(:name) + end + + def filter_team_id + @params[:team_id] + end + + def filter_status + @params[:status] + end + + def recordings + scope = Recording.for_club(@club) + .not_deleted + .includes(stream_session: { match: :team }) + .order(recorded_at: :desc, created_at: :desc) + scope = scope.where(team_id: filter_team_id) if filter_team_id.present? + apply_status_filter(scope) + end + + def filter_params + { team_id: filter_team_id, status: filter_status }.compact + end + + private + + def apply_status_filter(scope) + case filter_status + when "ready" + scope.ready + when "processing" + scope.where(status: "processing") + when "expired" + scope.where( + "recordings.status IN (?) OR (recordings.expires_at IS NOT NULL AND recordings.expires_at <= ?)", + %w[expired failed], Time.current + ) + else + scope + end + end + end +end diff --git a/backend/app/views/admin/club_recordings/index.html.erb b/backend/app/views/admin/club_recordings/index.html.erb new file mode 100644 index 0000000..a6faa14 --- /dev/null +++ b/backend/app/views/admin/club_recordings/index.html.erb @@ -0,0 +1,4 @@ +<% content_for :title, "Replay — #{@club.name}" %> +<% content_for :replay_archive_styles, true %> + +<%= render "recordings/club_archive", admin_mode: true %> diff --git a/backend/app/views/admin/clubs/show.html.erb b/backend/app/views/admin/clubs/show.html.erb index 5a35b0c..40f7101 100644 --- a/backend/app/views/admin/clubs/show.html.erb +++ b/backend/app/views/admin/clubs/show.html.erb @@ -3,6 +3,7 @@

<%= @club.name %>

Sport: <%= @club.sport %> + · <%= link_to "Archivio replay", admin_club_recordings_path(@club) %> · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @club.id) %> · <%= link_to "Canale YouTube piattaforma", admin_youtube_platform_path %>

@@ -38,7 +39,7 @@ <%= team.name %> <%= team.sport %> - <%= link_to "Partite e dettagli", admin_team_path(team) %> + <%= link_to "Partite e dettagli", admin_team_path(team) %> · <%= link_to "Replay", admin_club_recordings_path(@club, team_id: team.id) %> <% end %> diff --git a/backend/app/views/admin/teams/show.html.erb b/backend/app/views/admin/teams/show.html.erb index ed39b9e..f99060f 100644 --- a/backend/app/views/admin/teams/show.html.erb +++ b/backend/app/views/admin/teams/show.html.erb @@ -5,7 +5,7 @@

<%= @team.name %>

Sport: <%= @team.sport %>

<% if @team.club %> -

Società: <%= @team.club.name %> · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @team.club.id) %>

+

Società: <%= @team.club.name %> · <%= link_to "Replay squadra", admin_club_recordings_path(@team.club, team_id: @team.id) %> · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @team.club.id) %>

<% end %> <% yt = Youtube::TeamStatus.new(@team) %>

diff --git a/backend/app/views/layouts/admin.html.erb b/backend/app/views/layouts/admin.html.erb index 8adadb9..cbfcdd9 100644 --- a/backend/app/views/layouts/admin.html.erb +++ b/backend/app/views/layouts/admin.html.erb @@ -5,6 +5,9 @@ + <% if content_for?(:replay_archive_styles) %> + + <% end %> <% if controller_name == "dashboard" %> @@ -20,7 +23,7 @@ <%= link_to admin_ops_path, class: ("active" if controller_name == "ops") do %> Ops<% if ops_critical.positive? %> <%= ops_critical %><% end %> <% end %> - <%= link_to "Società e squadre", admin_clubs_path, class: ("active" if controller_name.in?(%w[clubs teams])) %> + <%= link_to "Società e squadre", admin_clubs_path, class: ("active" if controller_name.in?(%w[clubs teams club_recordings])) %> <%= link_to "Fatturazione", admin_billing_path, class: ("active" if controller_name.in?(%w[billing billing_invoices])) %> <%= link_to "YouTube", admin_youtube_platform_path, class: ("active" if controller_name == "youtube") %> <%= link_to "Sessions", admin_sessions_path, class: ("active" if controller_name == "sessions") %> diff --git a/backend/app/views/public/club_recordings/index.html.erb b/backend/app/views/public/club_recordings/index.html.erb index 71a2620..2ac7ddc 100644 --- a/backend/app/views/public/club_recordings/index.html.erb +++ b/backend/app/views/public/club_recordings/index.html.erb @@ -1,150 +1,6 @@ <% content_for :title, "Replay — #{@club.name}" %> <% content_for :robots, "noindex, nofollow" %> -

- <%= link_to "← #{@club.name}", public_club_path(@club), class: "back-link" %> - -

Archivio Replay

-

- Gestisci registrazioni, visibilità e download. - Privato = link non indicizzato, non compare in /replay pubblico. - Eliminando un replay vengono rimossi anche i file sul server e il video YouTube collegato. -

- -
-
-
<%= @stats[:available_count] %>
-
Replay disponibili
-
-
-
<%= @stats[:expiring_soon_count] %>
-
In scadenza (7 gg)
-
-
-
<%= number_to_human_size(@stats[:total_bytes]) %>
-
Spazio archivio
-
-
-
<%= @stats[:total_views] %>
-
Visualizzazioni totali
-
-
- - <%= form_with url: public_club_recordings_path(@club), method: :get, local: true, class: "replay-filter-form" do %> - - - - - <% end %> - - <% if @recordings.any? %> -
- - - - - - - - - - - - <% @recordings.each do |rec| %> - <% ent = rec.team.entitlements %> - <% can_play = rec.ready? && rec.replay_url.present? %> - - - - - - - - <% end %> - -
AnteprimaPartitaDettagliStato e visibilitàAzioni
- <% if can_play %> - <%= link_to rec.replay_url, class: "replay-archive__thumb", target: "_blank", rel: "noopener", title: "Guarda replay" do %> - <% if rec.thumbnail_url %> - - <% else %> - - <% end %> - - <% end %> - <% else %> -
"> - <% if rec.thumbnail_url %> - - <% else %> - - <% end %> -
- <% end %> -
- <%= form_with url: public_club_recording_path(@club, rec), method: :patch, local: true, class: "replay-archive__title-form" do %> - <%= hidden_field_tag :team_id, @filter_team_id if @filter_team_id.present? %> - <%= hidden_field_tag :status, @filter_status if @filter_status.present? %> - <%= text_field_tag "recording[title]", rec.title_or_default, class: "replay-archive__title-input", onchange: "this.form.submit()" %> - <% end %> - <%= rec.source_platform_label %> - - - <%= l_local(rec.recorded_at_or_fallback, format: :short) %> - · <%= rec.duration_label %> - · <%= rec.view_count %> vis. - - - <% if rec.expires_at %> - Scade <%= l_local(rec.expires_at, format: :short) %> - <% if rec.days_until_expiry && rec.days_until_expiry.positive? %> - (<%= rec.days_until_expiry %> gg) - <% end %> - <% else %> - Nessuna scadenza - <% end %> - - - <%= rec.status_label %> - <%= form_with url: public_club_recording_path(@club, rec), method: :patch, local: true, class: "replay-archive__privacy-form" do %> - <%= hidden_field_tag :team_id, @filter_team_id if @filter_team_id.present? %> - <%= hidden_field_tag :status, @filter_status if @filter_status.present? %> - <%= select_tag "recording[privacy_status]", - options_for_select([["Pubblico", "public"], ["Privato", "unlisted"]], rec.privacy_status), - class: "replay-archive__privacy-select", - onchange: "this.form.submit()" %> - <% end %> - -
- <% if ent.phone_download_enabled? && rec.ready? %> - <%= link_to "MP4", public_replay_download_path(rec.stream_session_id), class: "replay-archive__action replay-archive__action--secondary", title: "Scarica MP4" %> - <% end %> - <% if ent.premium_full? && ent.youtube_enabled? && rec.ready? && rec.youtube_video_id.blank? %> - <%= button_to "YT", public_club_recording_publish_youtube_path(@club, rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: "Pubblica su YouTube" %> - <% elsif rec.youtube_watch_url %> - <%= link_to "YT", rec.youtube_watch_url, class: "replay-archive__action replay-archive__action--secondary", target: "_blank", rel: "noopener", title: "Apri su YouTube" %> - <% end %> - <%= button_to public_club_recording_path(@club, rec), method: :delete, - params: { team_id: @filter_team_id, status: @filter_status }.compact, - class: "replay-archive__action replay-archive__action--danger", - title: "Elimina replay", - form: { data: { turbo_confirm: "Eliminare definitivamente questo replay? Verranno rimossi i file sul server e il video YouTube collegato." }, class: "replay-archive__action-form" } do %> - ✕ - <% end %> -
-
-
- <% else %> -

Nessuna registrazione ancora. I replay compaiono al termine delle dirette Premium.

- <% end %> +
+ <%= render "recordings/club_archive", admin_mode: false %>
diff --git a/backend/app/views/recordings/_club_archive.html.erb b/backend/app/views/recordings/_club_archive.html.erb new file mode 100644 index 0000000..9e613fc --- /dev/null +++ b/backend/app/views/recordings/_club_archive.html.erb @@ -0,0 +1,153 @@ +<%# locals: admin_mode: false %> +<% paths = @archive_paths %> +<% filter_params = @archive_filter_params %> + +
"> +

<%= link_to "← #{@club.name}", paths.back %>

+ +

Archivio Replay<% if local_assigns[:admin_mode] %> (admin)<% end %>

+

+ Gestisci registrazioni, visibilità e download. + Privato = link non indicizzato, non compare in /replay pubblico. + Eliminando un replay vengono rimossi anche i file sul server e il video YouTube collegato. +

+ +
+
+
<%= @stats[:available_count] %>
+
Replay disponibili
+
+
+
<%= @stats[:expiring_soon_count] %>
+
In scadenza (7 gg)
+
+
+
<%= number_to_human_size(@stats[:total_bytes]) %>
+
Spazio archivio
+
+
+
<%= @stats[:total_views] %>
+
Visualizzazioni totali
+
+
+ + <%= form_with url: paths.index, method: :get, local: true, class: "replay-filter-form" do %> + + + + + <% end %> + + <% if @recordings.any? %> +
+ + + + + + + + + + + + + <% @recordings.each do |rec| %> + <% ent = rec.team.entitlements %> + <% can_play = rec.ready? && rec.replay_url.present? %> + + + + + + + + + <% end %> + +
AnteprimaPartitaSquadraDettagliStato e visibilitàAzioni
+ <% if can_play %> + <%= link_to rec.replay_url, class: "replay-archive__thumb", target: "_blank", rel: "noopener", title: "Guarda replay" do %> + <% if rec.thumbnail_url %> + + <% else %> + + <% end %> + + <% end %> + <% else %> +
"> + <% if rec.thumbnail_url %> + + <% else %> + + <% end %> +
+ <% end %> +
+ <%= form_with url: paths.update.call(rec), method: :patch, local: true, class: "replay-archive__title-form" do %> + <% filter_params.each { |key, value| concat hidden_field_tag(key, value) } %> + <%= text_field_tag "recording[title]", rec.title_or_default, class: "replay-archive__title-input", onchange: "this.form.submit()" %> + <% end %> + <%= rec.source_platform_label %> + + <%= rec.team.name %> + + + <%= l_local(rec.recorded_at_or_fallback, format: :short) %> + · <%= rec.duration_label %> + · <%= rec.view_count %> vis. + + + <% if rec.expires_at %> + Scade <%= l_local(rec.expires_at, format: :short) %> + <% if rec.days_until_expiry && rec.days_until_expiry.positive? %> + (<%= rec.days_until_expiry %> gg) + <% end %> + <% else %> + Nessuna scadenza + <% end %> + + + <%= rec.status_label %> + <%= form_with url: paths.update.call(rec), method: :patch, local: true, class: "replay-archive__privacy-form" do %> + <% filter_params.each { |key, value| concat hidden_field_tag(key, value) } %> + <%= select_tag "recording[privacy_status]", + options_for_select([["Pubblico", "public"], ["Privato", "unlisted"]], rec.privacy_status), + class: "replay-archive__privacy-select", + onchange: "this.form.submit()" %> + <% end %> + +
+ <% if ent.phone_download_enabled? && rec.ready? %> + <%= link_to "MP4", public_replay_download_path(rec.stream_session_id), class: "replay-archive__action replay-archive__action--secondary", title: "Scarica MP4" %> + <% end %> + <% if ent.premium_full? && ent.youtube_enabled? && rec.ready? && rec.youtube_video_id.blank? %> + <%= button_to "YT", paths.publish_youtube.call(rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: "Pubblica su YouTube" %> + <% elsif rec.youtube_watch_url %> + <%= link_to "YT", rec.youtube_watch_url, class: "replay-archive__action replay-archive__action--secondary", target: "_blank", rel: "noopener", title: "Apri su YouTube" %> + <% end %> + <%= button_to paths.destroy.call(rec), method: :delete, + params: filter_params, + class: "replay-archive__action replay-archive__action--danger", + title: "Elimina replay", + form: { data: { turbo_confirm: "Eliminare definitivamente questo replay? Verranno rimossi i file sul server e il video YouTube collegato." }, class: "replay-archive__action-form" } do %> + ✕ + <% end %> +
+
+
+ <% else %> +

Nessuna registrazione per i filtri selezionati.

+ <% end %> +
diff --git a/backend/config/routes.rb b/backend/config/routes.rb index 70638af..4180943 100644 --- a/backend/config/routes.rb +++ b/backend/config/routes.rb @@ -93,6 +93,11 @@ Rails.application.routes.draw do delete :revoke_comped end resources :billing_invoices, only: %i[index new create edit update], controller: "billing_invoices" + resources :club_recordings, only: %i[index update destroy], path: "replays", as: :recordings do + member do + post :publish_youtube + end + end end resources :sessions, only: %i[index show] do member do diff --git a/backend/spec/requests/admin/club_recordings_spec.rb b/backend/spec/requests/admin/club_recordings_spec.rb new file mode 100644 index 0000000..4865121 --- /dev/null +++ b/backend/spec/requests/admin/club_recordings_spec.rb @@ -0,0 +1,50 @@ +require "rails_helper" + +RSpec.describe "Admin club replays", type: :request do + let!(:admin) { AdminAccount.create!(username: "ops", password: "secret123") } + let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") } + let!(:team) { club.teams.create!(name: "Tigers", 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.ago) } + let!(:session) do + StreamSession.create!(match: match, user: user, platform: "matchlivetv", status: "ended", ended_at: 1.hour.ago) + end + let!(:recording) do + Recording.create!( + stream_session: session, team: team, status: "ready", privacy_status: "public", + title: "Finale", + storage_key: "teams/#{team.id}/sessions/#{session.id}/replay.mp4", + expires_at: 30.days.from_now + ) + end + + before do + post admin_login_path, params: { username: "ops", password: "secret123" } + end + + it "mostra l'archivio replay della società" do + get admin_club_recordings_path(club) + + expect(response).to have_http_status(:ok) + expect(response.body).to include("Archivio Replay") + expect(response.body).to include("Finale") + expect(response.body).to include("Tigers") + end + + it "aggiorna titolo e privacy" do + patch admin_club_recording_path(club, recording), params: { recording: { title: "Nuovo titolo", privacy_status: "unlisted" } } + + expect(response).to redirect_to(admin_club_recordings_path(club)) + recording.reload + expect(recording.title).to eq("Nuovo titolo") + expect(recording.privacy_status).to eq("unlisted") + end + + it "elimina un replay" do + delete admin_club_recording_path(club, recording) + + expect(response).to redirect_to(admin_club_recordings_path(club)) + expect(recording.reload.status).to eq("expired") + expect(recording.deleted_at).to be_present + end +end diff --git a/backend/spec/services/recordings/club_archive_spec.rb b/backend/spec/services/recordings/club_archive_spec.rb new file mode 100644 index 0000000..f56484d --- /dev/null +++ b/backend/spec/services/recordings/club_archive_spec.rb @@ -0,0 +1,36 @@ +require "rails_helper" + +RSpec.describe Recordings::ClubArchive do + let!(:club) { Club.create!(name: "Club", sport: "volleyball", primary_color: "#e53935", secondary_color: "#ffffff") } + let!(:team_a) { club.teams.create!(name: "A", sport: "volleyball") } + let!(:team_b) { club.teams.create!(name: "B", sport: "volleyball") } + let!(:user) { User.create!(email: "coach@test.com", name: "Coach", password: "password123", role: "coach") } + let!(:match_a) { team_a.matches.create!(opponent_name: "Rival A", scheduled_at: 1.day.ago) } + let!(:match_b) { team_b.matches.create!(opponent_name: "Rival B", scheduled_at: 1.day.ago) } + let!(:session_a) do + StreamSession.create!(match: match_a, user: user, platform: "matchlivetv", status: "ended", ended_at: 1.hour.ago) + end + let!(:session_b) do + StreamSession.create!(match: match_b, user: user, platform: "matchlivetv", status: "ended", ended_at: 1.hour.ago) + end + let!(:ready_a) do + Recording.create!( + stream_session: session_a, team: team_a, status: "ready", privacy_status: "public", + storage_key: "teams/#{team_a.id}/sessions/#{session_a.id}/replay.mp4", + expires_at: 30.days.from_now + ) + end + let!(:processing_b) do + Recording.create!(stream_session: session_b, team: team_b, status: "processing", privacy_status: "unlisted") + end + + it "filtra per squadra" do + archive = described_class.new(club, params: { team_id: team_a.id }) + expect(archive.recordings).to contain_exactly(ready_a) + end + + it "filtra per stato ready" do + archive = described_class.new(club, params: { status: "ready" }) + expect(archive.recordings).to contain_exactly(ready_a) + end +end