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 @@
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 @@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" %> -
- 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.
-
| Anteprima | -Partita | -Dettagli | -Stato 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 %>
- ">
- <% if rec.thumbnail_url %>
-
- <% 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 %> - - | -- - <%= l_local(rec.recorded_at_or_fallback, format: :short) %> - · <%= rec.duration_label %> - · <%= rec.view_count %> vis. - - - | -- <%= 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 %>
-
- |
-
Nessuna registrazione ancora. I replay compaiono al termine delle dirette Premium.
- <% end %> +<%= link_to "← #{@club.name}", paths.back %>
+ +
+ 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.
+
| Anteprima | +Partita | +Squadra | +Dettagli | +Stato 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 %>
+ ">
+ <% if rec.thumbnail_url %>
+
+ <% 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 %> + + | ++ + | ++ + <%= l_local(rec.recorded_at_or_fallback, format: :short) %> + · <%= rec.duration_label %> + · <%= rec.view_count %> vis. + + + | ++ <%= 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 %>
+
+ |
+
Nessuna registrazione per i filtri selezionati.
+ <% end %> +