Permette agli admin di prolungare la scadenza dei replay.
Aggiunge data personalizzata e pulsanti rapidi nell'archivio admin così il job di purge non elimina i video fino alla nuova scadenza. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,7 +13,12 @@ module Recordings
|
|||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
permitted = params.require(:recording).permit(:privacy_status, :title)
|
if admin_expiry_update_requested?
|
||||||
|
apply_admin_expiry_update!
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
permitted = recording_update_params
|
||||||
attrs = {}
|
attrs = {}
|
||||||
privacy = permitted[:privacy_status]
|
privacy = permitted[:privacy_status]
|
||||||
attrs[:privacy_status] = privacy if privacy.in?(Recording::PRIVACY_STATUSES)
|
attrs[:privacy_status] = privacy if privacy.in?(Recording::PRIVACY_STATUSES)
|
||||||
@@ -25,6 +30,8 @@ module Recordings
|
|||||||
redirect_to archive_index_path, notice: "Replay aggiornato"
|
redirect_to archive_index_path, notice: "Replay aggiornato"
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
redirect_to archive_index_path, alert: e.record.errors.full_messages.join(", ")
|
redirect_to archive_index_path, alert: e.record.errors.full_messages.join(", ")
|
||||||
|
rescue Recordings::ExtendExpiry::Error => e
|
||||||
|
redirect_to archive_index_path, alert: e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -87,5 +94,30 @@ module Recordings
|
|||||||
def archive_namespace
|
def archive_namespace
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def recording_update_params
|
||||||
|
params.require(:recording).permit(:privacy_status, :title, :expires_at, :extend_days)
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin_expiry_update_requested?
|
||||||
|
return false unless archive_namespace == :admin
|
||||||
|
|
||||||
|
permitted = recording_update_params
|
||||||
|
permitted[:extend_days].present? || permitted[:expires_at].present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_admin_expiry_update!
|
||||||
|
permitted = recording_update_params
|
||||||
|
extender = Recordings::ExtendExpiry.new(@recording)
|
||||||
|
|
||||||
|
if permitted[:extend_days].present?
|
||||||
|
extender.by_days(permitted[:extend_days])
|
||||||
|
redirect_to archive_index_path, notice: "Scadenza replay prolungata di #{permitted[:extend_days].to_i} giorni"
|
||||||
|
else
|
||||||
|
expires_at = Time.zone.parse(permitted[:expires_at].to_s)&.end_of_day
|
||||||
|
extender.to_time(expires_at)
|
||||||
|
redirect_to archive_index_path, notice: "Scadenza replay aggiornata"
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
39
backend/app/services/recordings/extend_expiry.rb
Normal file
39
backend/app/services/recordings/extend_expiry.rb
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
module Recordings
|
||||||
|
class ExtendExpiry
|
||||||
|
class Error < StandardError; end
|
||||||
|
|
||||||
|
def initialize(recording)
|
||||||
|
@recording = recording
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_time(expires_at)
|
||||||
|
raise Error, "Data scadenza obbligatoria" if expires_at.blank?
|
||||||
|
|
||||||
|
parsed = expires_at.is_a?(Time) || expires_at.is_a?(ActiveSupport::TimeWithZone) ? expires_at : Time.zone.parse(expires_at.to_s)
|
||||||
|
raise Error, "Data non valida" unless parsed
|
||||||
|
|
||||||
|
apply(parsed)
|
||||||
|
end
|
||||||
|
|
||||||
|
def by_days(days)
|
||||||
|
count = days.to_i
|
||||||
|
raise Error, "Giorni non validi" if count <= 0
|
||||||
|
|
||||||
|
base = [@recording.expires_at, Time.current].compact.max
|
||||||
|
apply(base + count.days)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def apply(new_expires_at)
|
||||||
|
raise Error, "Replay già eliminato" if @recording.deleted?
|
||||||
|
raise Error, "La scadenza deve essere nel futuro" unless new_expires_at > Time.current
|
||||||
|
|
||||||
|
@recording.update!(
|
||||||
|
expires_at: new_expires_at,
|
||||||
|
expiry_warning_sent_at: nil
|
||||||
|
)
|
||||||
|
@recording
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -112,6 +112,26 @@
|
|||||||
Nessuna scadenza
|
Nessuna scadenza
|
||||||
<% end %>
|
<% end %>
|
||||||
</span>
|
</span>
|
||||||
|
<% if local_assigns[:admin_mode] && !rec.deleted? && rec.expires_at.present? %>
|
||||||
|
<%= form_with url: paths.update.call(rec), method: :patch, local: true, class: "replay-archive__expiry-form" do %>
|
||||||
|
<% filter_params.each { |key, value| concat hidden_field_tag(key, value) } %>
|
||||||
|
<%= date_field_tag "recording[expires_at]",
|
||||||
|
rec.expires_at.to_date,
|
||||||
|
min: Date.tomorrow,
|
||||||
|
class: "replay-archive__expiry-input",
|
||||||
|
title: "Nuova data di scadenza" %>
|
||||||
|
<%= submit_tag "Salva", class: "replay-archive__expiry-save" %>
|
||||||
|
<% end %>
|
||||||
|
<div class="replay-archive__expiry-quick">
|
||||||
|
<% [[30, "+30 gg"], [90, "+90 gg"], [180, "+180 gg"]].each do |days, label| %>
|
||||||
|
<%= button_to label, paths.update.call(rec),
|
||||||
|
method: :patch,
|
||||||
|
params: filter_params.merge(recording: { extend_days: days }),
|
||||||
|
class: "replay-archive__expiry-extend",
|
||||||
|
title: "Prolunga la scadenza di #{days} giorni" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td class="replay-archive-table__col-status">
|
<td class="replay-archive-table__col-status">
|
||||||
<span class="replay-archive__status replay-archive__status--<%= rec.status %>"><%= rec.status_label %></span>
|
<span class="replay-archive__status replay-archive__status--<%= rec.status %>"><%= rec.status_label %></span>
|
||||||
|
|||||||
@@ -1593,6 +1593,55 @@ a.replay-archive__thumb:hover {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-input {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-save {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #555;
|
||||||
|
background: #2a2a2a;
|
||||||
|
color: #ddd;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-quick {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-extend {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #444;
|
||||||
|
background: #222;
|
||||||
|
color: #aaa;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.replay-archive__expiry-extend:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
.visually-hidden {
|
.visually-hidden {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 1px;
|
width: 1px;
|
||||||
|
|||||||
@@ -47,4 +47,29 @@ RSpec.describe "Admin club replays", type: :request do
|
|||||||
expect(recording.reload.status).to eq("expired")
|
expect(recording.reload.status).to eq("expired")
|
||||||
expect(recording.deleted_at).to be_present
|
expect(recording.deleted_at).to be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "prolunga la scadenza con giorni aggiuntivi" do
|
||||||
|
original = recording.expires_at
|
||||||
|
recording.update!(expires_at: 1.hour.ago, expiry_warning_sent_at: Time.current)
|
||||||
|
|
||||||
|
patch admin_club_recording_path(club, recording), params: { recording: { extend_days: 30 } }
|
||||||
|
|
||||||
|
expect(response).to redirect_to(admin_club_recordings_path(club))
|
||||||
|
recording.reload
|
||||||
|
expect(recording.expires_at).to be > Time.current
|
||||||
|
expect(recording.expires_at).to be_within(2.seconds).of(30.days.from_now)
|
||||||
|
expect(recording.expiry_warning_sent_at).to be_nil
|
||||||
|
expect(Recording.expired_pending_purge).not_to include(recording)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "imposta una nuova data di scadenza" do
|
||||||
|
new_date = 60.days.from_now.to_date
|
||||||
|
|
||||||
|
patch admin_club_recording_path(club, recording), params: { recording: { expires_at: new_date } }
|
||||||
|
|
||||||
|
expect(response).to redirect_to(admin_club_recordings_path(club))
|
||||||
|
recording.reload
|
||||||
|
expect(recording.expires_at.to_date).to eq(new_date)
|
||||||
|
expect(recording.expiry_warning_sent_at).to be_nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
64
backend/spec/services/recordings/extend_expiry_spec.rb
Normal file
64
backend/spec/services/recordings/extend_expiry_spec.rb
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
RSpec.describe Recordings::ExtendExpiry do
|
||||||
|
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",
|
||||||
|
storage_key: "teams/#{team.id}/sessions/#{session.id}/replay.mp4",
|
||||||
|
expires_at: 2.days.from_now,
|
||||||
|
expiry_warning_sent_at: 1.day.ago
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "imposta una nuova data di scadenza e resetta l'avviso" do
|
||||||
|
new_expiry = 45.days.from_now
|
||||||
|
|
||||||
|
described_class.new(recording).to_time(new_expiry)
|
||||||
|
|
||||||
|
recording.reload
|
||||||
|
expect(recording.expires_at).to be_within(1.second).of(new_expiry)
|
||||||
|
expect(recording.expiry_warning_sent_at).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prolunga dalla scadenza corrente" do
|
||||||
|
original = recording.expires_at
|
||||||
|
|
||||||
|
described_class.new(recording).by_days(30)
|
||||||
|
|
||||||
|
recording.reload
|
||||||
|
expect(recording.expires_at).to be_within(1.second).of(original + 30.days)
|
||||||
|
expect(recording.expiry_warning_sent_at).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prolunga da oggi se la scadenza è già passata" do
|
||||||
|
recording.update!(expires_at: 2.days.ago)
|
||||||
|
|
||||||
|
described_class.new(recording).by_days(10)
|
||||||
|
|
||||||
|
recording.reload
|
||||||
|
expect(recording.expires_at).to be_within(1.second).of(10.days.from_now)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "esclude il replay dalla coda di purge dopo il prolungamento" do
|
||||||
|
recording.update!(expires_at: 1.hour.ago)
|
||||||
|
|
||||||
|
expect(Recording.expired_pending_purge).to include(recording)
|
||||||
|
|
||||||
|
described_class.new(recording).by_days(30)
|
||||||
|
|
||||||
|
expect(Recording.expired_pending_purge).not_to include(recording.reload)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "rifiuta date nel passato" do
|
||||||
|
expect do
|
||||||
|
described_class.new(recording).to_time(1.day.ago)
|
||||||
|
end.to raise_error(Recordings::ExtendExpiry::Error, /futuro/)
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user