mirror of
https://github.com/jcwimer/wrestlingApp
synced 2026-03-25 01:14:43 +00:00
Added tournament backups to the database and added pages to restore and create backups
This commit is contained in:
78
app/controllers/tournament_backups_controller.rb
Normal file
78
app/controllers/tournament_backups_controller.rb
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
class TournamentBackupsController < ApplicationController
|
||||||
|
before_action :set_tournament
|
||||||
|
before_action :set_tournament_backup, only: [:show, :destroy, :restore]
|
||||||
|
before_action :check_access_manage
|
||||||
|
|
||||||
|
# GET /tournament/:tournament_id/tournament_backups
|
||||||
|
def index
|
||||||
|
@tournament_backups = @tournament.tournament_backups.order(created_at: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
# GET /tournament/:tournament_id/tournament_backups/:id
|
||||||
|
def show
|
||||||
|
end
|
||||||
|
|
||||||
|
# DELETE /tournament/:tournament_id/tournament_backups/:id
|
||||||
|
def destroy
|
||||||
|
if @tournament_backup.destroy
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), notice: 'Backup was successfully deleted.'
|
||||||
|
else
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), alert: 'Failed to delete the backup.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /tournament/:tournament_id/tournament_backups/create
|
||||||
|
def create
|
||||||
|
TournamentBackupService.new(@tournament, 'Manual backup').create_backup
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), notice: 'Backup was successfully created. It will show up soon, check your background jobs for status.'
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /tournament/:tournament_id/tournament_backups/:id/restore
|
||||||
|
def restore
|
||||||
|
WrestlingdevImporter.new(@tournament, @tournament_backup).import
|
||||||
|
redirect_to tournament_path(@tournament), notice: 'Restore has successfully been submitted, please check your background jobs to see if it has finished.'
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /tournament/:tournament_id/tournament_backups/import_manual
|
||||||
|
def import_manual
|
||||||
|
import_text = params[:tournament][:import_text]
|
||||||
|
if import_text.blank?
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), alert: 'Import text cannot be blank.'
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
begin
|
||||||
|
|
||||||
|
# Create a temporary backup object
|
||||||
|
backup = TournamentBackup.new(
|
||||||
|
tournament: @tournament,
|
||||||
|
backup_data: Base64.encode64(import_text),
|
||||||
|
backup_reason: 'Manual Import'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Pass the backup object to the importer
|
||||||
|
WrestlingdevImporter.new(@tournament, backup).import
|
||||||
|
|
||||||
|
redirect_to tournament_path(@tournament), notice: 'Restore has successfully been submitted, please check your background jobs to see if it has finished.'
|
||||||
|
rescue JSON::ParserError => e
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), alert: "Failed to parse JSON: #{e.message}"
|
||||||
|
rescue StandardError => e
|
||||||
|
redirect_to tournament_tournament_backups_path(@tournament), alert: "An error occurred: #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_tournament
|
||||||
|
@tournament = Tournament.find(params[:tournament_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_tournament_backup
|
||||||
|
@tournament_backup = @tournament.tournament_backups.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_access_manage
|
||||||
|
authorize! :manage, @tournament
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
class TournamentsController < ApplicationController
|
class TournamentsController < ApplicationController
|
||||||
before_action :set_tournament, only: [:reset_bout_board,:calculate_team_scores, :import,:export,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:brackets,:generate_matches,:bracket,:all_brackets]
|
before_action :set_tournament, only: [:reset_bout_board,:calculate_team_scores,:bout_sheets,:swap,:weigh_in_sheet,:error,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:remove_delegate,:school_delegate,:delegate,:matches,:weigh_in,:weigh_in_weight,:create_custom_weights,:show,:edit,:update,:destroy,:up_matches,:no_matches,:team_scores,:brackets,:generate_matches,:bracket,:all_brackets]
|
||||||
before_action :check_access_manage, only: [:reset_bout_board,:calculate_team_scores, :import,:export,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches]
|
before_action :check_access_manage, only: [:reset_bout_board,:calculate_team_scores,:swap,:weigh_in_sheet,:teampointadjust,:remove_teampointadjust,:remove_school_delegate,:school_delegate,:weigh_in,:weigh_in_weight,:create_custom_weights,:update,:edit,:generate_matches,:matches]
|
||||||
before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate]
|
before_action :check_access_destroy, only: [:destroy,:delegate,:remove_delegate]
|
||||||
before_action :check_tournament_errors, only: [:generate_matches]
|
before_action :check_tournament_errors, only: [:generate_matches]
|
||||||
before_action :check_for_matches, only: [:up_matches,:bracket,:all_brackets]
|
before_action :check_for_matches, only: [:up_matches,:bracket,:all_brackets]
|
||||||
@@ -10,10 +10,6 @@ class TournamentsController < ApplicationController
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def export
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
def calculate_team_scores
|
def calculate_team_scores
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
if @tournament.calculate_all_team_scores
|
if @tournament.calculate_all_team_scores
|
||||||
@@ -23,16 +19,6 @@ class TournamentsController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def import
|
|
||||||
import_text = params[:tournament][:import_text]
|
|
||||||
respond_to do |format|
|
|
||||||
if WrestlingdevImporter.new(@tournament,import_text).import
|
|
||||||
format.html { redirect_to "/tournaments/#{@tournament.id}", notice: 'Import is on-going. This will take 1-5 minutes.' }
|
|
||||||
format.json { head :no_content }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def swap
|
def swap
|
||||||
@wrestler = Wrestler.find(params[:wrestler][:originalId])
|
@wrestler = Wrestler.find(params[:wrestler][:originalId])
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class Tournament < ApplicationRecord
|
|||||||
has_many :matches, dependent: :destroy
|
has_many :matches, dependent: :destroy
|
||||||
has_many :delegates, class_name: "TournamentDelegate"
|
has_many :delegates, class_name: "TournamentDelegate"
|
||||||
has_many :mat_assignment_rules, dependent: :destroy
|
has_many :mat_assignment_rules, dependent: :destroy
|
||||||
|
has_many :tournament_backups, dependent: :destroy
|
||||||
|
|
||||||
validates :date, :name, :tournament_type, :address, :director, :director_email , presence: true
|
validates :date, :name, :tournament_type, :address, :director, :director_email , presence: true
|
||||||
|
|
||||||
@@ -228,4 +229,8 @@ class Tournament < ApplicationRecord
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_backup()
|
||||||
|
TournamentBackupService.new(self, "Manual backup").create_backup
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
5
app/models/tournament_backup.rb
Normal file
5
app/models/tournament_backup.rb
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
class TournamentBackup < ApplicationRecord
|
||||||
|
belongs_to :tournament
|
||||||
|
|
||||||
|
validates :backup_data, presence: true
|
||||||
|
end
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
class TournamentBackupService
|
||||||
|
def initialize(tournament, reason)
|
||||||
|
@tournament = tournament
|
||||||
|
@reason = reason
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_backup
|
||||||
|
if Rails.env.production?
|
||||||
|
self.delay(:job_owner_id => @tournament.id, :job_owner_type => "Create a backup").create_backup_raw
|
||||||
|
else
|
||||||
|
self.create_backup_raw
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_backup_raw
|
||||||
|
# Generate the JSON directly in Ruby and encode it
|
||||||
|
backup_data = Base64.encode64(generate_json.to_json)
|
||||||
|
|
||||||
|
begin
|
||||||
|
# Save the backup with encoded data
|
||||||
|
TournamentBackup.create!(tournament: @tournament, backup_data: backup_data, backup_reason: @reason)
|
||||||
|
Rails.logger.info("Backup created successfully for tournament ##{@tournament.id}")
|
||||||
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
|
Rails.logger.error("Failed to save backup: #{e.message}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_json
|
||||||
|
{
|
||||||
|
tournament: {
|
||||||
|
attributes: @tournament.attributes,
|
||||||
|
schools: @tournament.schools.map(&:attributes),
|
||||||
|
weights: @tournament.weights.map(&:attributes),
|
||||||
|
mats: @tournament.mats.map(&:attributes),
|
||||||
|
mat_assignment_rules: @tournament.mat_assignment_rules.map do |rule|
|
||||||
|
rule.attributes.merge(
|
||||||
|
mat: Mat.find_by(id: rule.mat_id)&.attributes.slice("name"),
|
||||||
|
weight_classes: rule.weight_classes.map do |weight_id|
|
||||||
|
Weight.find_by(id: weight_id)&.max
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
wrestlers: @tournament.wrestlers.map do |wrestler|
|
||||||
|
wrestler.attributes.merge(
|
||||||
|
school: wrestler.school&.attributes,
|
||||||
|
weight: wrestler.weight&.attributes
|
||||||
|
)
|
||||||
|
end,
|
||||||
|
matches: @tournament.matches.sort_by(&:bout_number).map do |match|
|
||||||
|
match.attributes.merge(
|
||||||
|
w1_name: Wrestler.find_by(id: match.w1)&.name,
|
||||||
|
w2_name: Wrestler.find_by(id: match.w2)&.name,
|
||||||
|
winner_name: Wrestler.find_by(id: match.winner_id)&.name,
|
||||||
|
weight: Weight.find_by(id: match.weight_id)&.attributes,
|
||||||
|
mat: Mat.find_by(id: match.mat_id)&.attributes
|
||||||
|
)
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
class WrestlingdevImporter
|
class WrestlingdevImporter
|
||||||
|
|
||||||
##### Note, the json contains id's for each row in the tables as well as it's associations
|
##### Note, the json contains id's for each row in the tables as well as its associations
|
||||||
##### this ignores those ids and uses this tournament id and then looks up associations based on name
|
##### this ignores those ids and uses this tournament id and then looks up associations based on name
|
||||||
##### and this tournament id
|
##### and this tournament id
|
||||||
def initialize(tournament, import_json)
|
def initialize(tournament, backup)
|
||||||
@tournament = tournament
|
@tournament = tournament
|
||||||
@import_data = JSON.parse(import_json)
|
@import_data = JSON.parse(Base64.decode64(backup.backup_data))
|
||||||
end
|
end
|
||||||
|
|
||||||
def import
|
def import
|
||||||
@@ -26,6 +26,7 @@ class WrestlingdevImporter
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy_all
|
def destroy_all
|
||||||
|
@tournament.mat_assignment_rules.destroy_all
|
||||||
@tournament.mats.destroy_all
|
@tournament.mats.destroy_all
|
||||||
@tournament.matches.destroy_all
|
@tournament.matches.destroy_all
|
||||||
@tournament.schools.each do |school|
|
@tournament.schools.each do |school|
|
||||||
@@ -42,6 +43,8 @@ class WrestlingdevImporter
|
|||||||
parse_mats(@import_data["tournament"]["mats"])
|
parse_mats(@import_data["tournament"]["mats"])
|
||||||
parse_wrestlers(@import_data["tournament"]["wrestlers"])
|
parse_wrestlers(@import_data["tournament"]["wrestlers"])
|
||||||
parse_matches(@import_data["tournament"]["matches"])
|
parse_matches(@import_data["tournament"]["matches"])
|
||||||
|
puts "Parsing mat assignment rules"
|
||||||
|
parse_mat_assignment_rules(@import_data["tournament"]["mat_assignment_rules"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_tournament(attributes)
|
def parse_tournament(attributes)
|
||||||
@@ -70,6 +73,34 @@ class WrestlingdevImporter
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_mat_assignment_rules(mat_assignment_rules)
|
||||||
|
mat_assignment_rules.each do |rule_attributes|
|
||||||
|
mat_name = rule_attributes.dig("mat", "name")
|
||||||
|
mat = Mat.find_by(name: mat_name, tournament_id: @tournament.id)
|
||||||
|
|
||||||
|
# Map max values of weight_classes to their new IDs
|
||||||
|
new_weight_classes = rule_attributes["weight_classes"].map do |max_value|
|
||||||
|
Weight.find_by(max: max_value, tournament_id: @tournament.id)&.id
|
||||||
|
end.compact
|
||||||
|
|
||||||
|
# Extract bracket_positions and rounds
|
||||||
|
bracket_positions = rule_attributes["bracket_positions"]
|
||||||
|
rounds = rule_attributes["rounds"]
|
||||||
|
|
||||||
|
rule_attributes.except!("id", "mat", "tournament_id", "weight_classes")
|
||||||
|
|
||||||
|
MatAssignmentRule.create(
|
||||||
|
rule_attributes.merge(
|
||||||
|
tournament_id: @tournament.id,
|
||||||
|
mat_id: mat&.id,
|
||||||
|
weight_classes: new_weight_classes,
|
||||||
|
bracket_positions: bracket_positions,
|
||||||
|
rounds: rounds
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_wrestlers(wrestlers)
|
def parse_wrestlers(wrestlers)
|
||||||
wrestlers.each do |wrestler_attributes|
|
wrestlers.each do |wrestler_attributes|
|
||||||
school = School.find_by(name: wrestler_attributes["school"]["name"], tournament_id: @tournament.id)
|
school = School.find_by(name: wrestler_attributes["school"]["name"], tournament_id: @tournament.id)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<li><%= link_to "Full Screen Bout Board" , "/tournaments/#{@tournament.id}/up_matches?print=true" , target: :_blank %></li>
|
<li><%= link_to "Full Screen Bout Board" , "/tournaments/#{@tournament.id}/up_matches?print=true" , target: :_blank %></li>
|
||||||
<li><%= link_to "Deduct Team Points" , "/tournaments/#{@tournament.id}/teampointadjust" %></li>
|
<li><%= link_to "Deduct Team Points" , "/tournaments/#{@tournament.id}/teampointadjust" %></li>
|
||||||
<li><%= link_to "View All Mat Assignment Rules", tournament_mat_assignment_rules_path(@tournament) %></li>
|
<li><%= link_to "View All Mat Assignment Rules", tournament_mat_assignment_rules_path(@tournament) %></li>
|
||||||
|
<li><%= link_to 'Manage Backups', tournament_tournament_backups_path(@tournament) %></li>
|
||||||
<li><%= link_to "Reset Bout Board", reset_bout_board_tournament_path(@tournament), method: :post, data: { confirm: "Are you sure you want to reset the bout board?" } %></li>
|
<li><%= link_to "Reset Bout Board", reset_bout_board_tournament_path(@tournament), method: :post, data: { confirm: "Are you sure you want to reset the bout board?" } %></li>
|
||||||
<% if can? :destroy, @tournament %>
|
<% if can? :destroy, @tournament %>
|
||||||
<li><%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %></li>
|
<li><%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %></li>
|
||||||
|
|||||||
38
app/views/tournament_backups/index.html.erb
Normal file
38
app/views/tournament_backups/index.html.erb
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<h3>Tournament Backups</h3>
|
||||||
|
<p>You can click on the timestamp to view the backup text. You can manually store this anywhere you'd like and then paste it into the manual import field below.
|
||||||
|
Doing this is risky, if the text is formatted incorrectly (like Microsoft Word changing the quotation marks), it will not import properly
|
||||||
|
and will also delete all of your current data. It's best to use the create backup function.</p>
|
||||||
|
<br>
|
||||||
|
<table class="table table-hover table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Backup Created At</th>
|
||||||
|
<th>Backup Reason</th>
|
||||||
|
<th><%= link_to ' Create New Backup', tournament_tournament_backups_path(@tournament), method: :post, class: 'fas fa-plus'%></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% @tournament_backups.each do |backup| %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= link_to backup.created_at.strftime('%Y-%m-%d %H:%M:%S'), tournament_tournament_backup_path(@tournament, backup, print: true), target: '_blank', class: 'text-decoration-none' %>
|
||||||
|
</td>
|
||||||
|
<td><%= backup.backup_reason.presence || 'No reason provided' %></td>
|
||||||
|
<td>
|
||||||
|
<%= link_to '', restore_tournament_tournament_backup_path(@tournament, backup), method: :post, data: { confirm: "This will restore the backup from #{backup.created_at.strftime('%Y-%m-%d %H:%M:%S')}. It will delete all current data from the tournament in order to restore the backup." }, class: 'fas fa-undo-alt text-warning', title: 'Restore Backup' %>
|
||||||
|
<%= link_to '', tournament_tournament_backup_path(@tournament, backup), method: :delete, data: { confirm: 'Are you sure you want to delete this backup?' }, class: 'fas fa-trash-alt', title: 'Delete Backup' %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br><br>
|
||||||
|
<h3>Import Manual Backup</h3>
|
||||||
|
<p>Paste the backup text here. Note, if this is formatted wrong, you'll need to restore a backup from above to fix it and you'll see an error in your background jobs.</p>
|
||||||
|
<%= form_for(:tournament, url: import_manual_tournament_tournament_backups_path(@tournament)) do |f| %>
|
||||||
|
<div class="field">
|
||||||
|
<%= f.label 'Import text' %><br>
|
||||||
|
<%= f.text_area :import_text, cols: "30", rows: "20" %>
|
||||||
|
</div>
|
||||||
|
<%= submit_tag "Import", class: "btn btn-success", data: { confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' } %>
|
||||||
|
<% end %>
|
||||||
1
app/views/tournament_backups/show.html.erb
Normal file
1
app/views/tournament_backups/show.html.erb
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<%= JSON.pretty_generate(JSON.parse(Base64.decode64(@tournament_backup.backup_data))) %>
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<% if can? :manage, @tournament %>
|
|
||||||
<br><br>
|
|
||||||
<h3>Import Data</h3>
|
|
||||||
<%= form_for(Tournament.new, url: import_url(@tournament)) do |f| %>
|
|
||||||
<div class="field">
|
|
||||||
<%= f.label 'Import text' %><br>
|
|
||||||
<%= f.text_area :import_text, cols: "30", rows: "20" %>
|
|
||||||
</div>
|
|
||||||
<%= submit_tag "Import", :class=>"btn btn-success", data: { confirm: 'Are you sure? This will delete everything for the current tournament and restore it with the backup text pasted below.' }%>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if can? :manage, @tournament %>
|
<% if can? :manage, @tournament %>
|
||||||
<br>
|
<br><br>
|
||||||
<h3>Background Jobs</h3>
|
<h3>Background Jobs</h3>
|
||||||
<p>This is a list of queued or running background jobs. Match generation, bracket advancement, team score calculation, etc.</p>
|
<p>This is a list of queued or running background jobs. Match generation, bracket advancement, team score calculation, etc.</p>
|
||||||
<table class="table table-hover table-condensed">
|
<table class="table table-hover table-condensed">
|
||||||
@@ -148,9 +148,4 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% end %>
|
<% end %>
|
||||||
<br>
|
<br><br>
|
||||||
<br>
|
|
||||||
<%= render 'import_form' %>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ Wrestling::Application.routes.draw do
|
|||||||
member do
|
member do
|
||||||
post :reset_bout_board
|
post :reset_bout_board
|
||||||
end
|
end
|
||||||
|
resources :tournament_backups, only: [:index, :show, :destroy] do
|
||||||
|
post :create, on: :collection
|
||||||
|
post :restore, on: :member
|
||||||
|
post :import_manual, on: :collection
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :schools
|
resources :schools
|
||||||
@@ -57,8 +62,6 @@ Wrestling::Application.routes.draw do
|
|||||||
delete 'tournaments/:id/:teampointadjust/remove_teampointadjust' => 'tournaments#remove_teampointadjust'
|
delete 'tournaments/:id/:teampointadjust/remove_teampointadjust' => 'tournaments#remove_teampointadjust'
|
||||||
get 'tournaments/:id/error' => 'tournaments#error'
|
get 'tournaments/:id/error' => 'tournaments#error'
|
||||||
post "/tournaments/:id/swap" => "tournaments#swap", :as => :swap_wrestlers
|
post "/tournaments/:id/swap" => "tournaments#swap", :as => :swap_wrestlers
|
||||||
get 'tournaments/:id/export' => "tournaments#export"
|
|
||||||
post "/tournaments/:id/import" => "tournaments#import", :as => :import
|
|
||||||
get "/tournaments/:id/brackets" => "tournaments#show"
|
get "/tournaments/:id/brackets" => "tournaments#show"
|
||||||
put "/tournaments/:id/calculate_team_scores", :to => "tournaments#calculate_team_scores"
|
put "/tournaments/:id/calculate_team_scores", :to => "tournaments#calculate_team_scores"
|
||||||
|
|
||||||
|
|||||||
16
db/migrate/20241224132705_create_tournament_backups.rb
Normal file
16
db/migrate/20241224132705_create_tournament_backups.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class CreateTournamentBackups < ActiveRecord::Migration[7.0]
|
||||||
|
def up
|
||||||
|
create_table :tournament_backups do |t|
|
||||||
|
t.integer :tournament_id, null: false, foreign_key: true
|
||||||
|
t.text :backup_data, null: false, limit: 4294967295 # Use LONGTEXT for large backups
|
||||||
|
t.string :backup_reason
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
# Drop the table
|
||||||
|
drop_table :tournament_backups
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
11
db/schema.rb
11
db/schema.rb
@@ -10,7 +10,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do
|
ActiveRecord::Schema[7.2].define(version: 2024_12_24_132705) do
|
||||||
create_table "delayed_jobs", force: :cascade do |t|
|
create_table "delayed_jobs", force: :cascade do |t|
|
||||||
t.integer "priority", default: 0, null: false
|
t.integer "priority", default: 0, null: false
|
||||||
t.integer "attempts", default: 0, null: false
|
t.integer "attempts", default: 0, null: false
|
||||||
@@ -99,6 +99,14 @@ ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do
|
|||||||
t.index ["wrestler_id"], name: "index_teampointadjusts_on_wrestler_id"
|
t.index ["wrestler_id"], name: "index_teampointadjusts_on_wrestler_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "tournament_backups", force: :cascade do |t|
|
||||||
|
t.integer "tournament_id", null: false
|
||||||
|
t.text "backup_data", limit: 4294967295, null: false
|
||||||
|
t.string "backup_reason"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
end
|
||||||
|
|
||||||
create_table "tournament_delegates", force: :cascade do |t|
|
create_table "tournament_delegates", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "tournament_id"
|
t.integer "tournament_id"
|
||||||
@@ -166,5 +174,4 @@ ActiveRecord::Schema[7.1].define(version: 2024_10_27_203209) do
|
|||||||
t.index ["school_id"], name: "index_wrestlers_on_school_id"
|
t.index ["school_id"], name: "index_wrestlers_on_school_id"
|
||||||
t.index ["weight_id"], name: "index_wrestlers_on_weight_id"
|
t.index ["weight_id"], name: "index_wrestlers_on_weight_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
220
test/controllers/tournament_backups_controller_test.rb
Normal file
220
test/controllers/tournament_backups_controller_test.rb
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class TournamentBackupsControllerTest < ActionController::TestCase
|
||||||
|
include Devise::Test::ControllerHelpers
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@tournament = Tournament.find(1)
|
||||||
|
TournamentBackupService.new(@tournament, 'Manual backup').create_backup
|
||||||
|
@backup = @tournament.tournament_backups.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_in_owner
|
||||||
|
sign_in users(:one)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_in_non_owner
|
||||||
|
sign_in users(:two)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_in_delegate
|
||||||
|
sign_in users(:three)
|
||||||
|
end
|
||||||
|
|
||||||
|
def sign_in_school_delegate
|
||||||
|
sign_in users(:four)
|
||||||
|
end
|
||||||
|
|
||||||
|
def success
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirect
|
||||||
|
assert_redirected_to '/static_pages/not_allowed'
|
||||||
|
end
|
||||||
|
|
||||||
|
# Index endpoint tests
|
||||||
|
test "logged in tournament owner can access index" do
|
||||||
|
sign_in_owner
|
||||||
|
get :index, params: { tournament_id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "logged in delegate can access index" do
|
||||||
|
sign_in_delegate
|
||||||
|
get :index, params: { tournament_id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-tournament owner cannot access index" do
|
||||||
|
sign_in_non_owner
|
||||||
|
get :index, params: { tournament_id: @tournament.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school delegate cannot access index" do
|
||||||
|
sign_in_school_delegate
|
||||||
|
get :index, params: { tournament_id: @tournament.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
# Show endpoint tests
|
||||||
|
test "tournament owner can view a backup" do
|
||||||
|
sign_in_owner
|
||||||
|
get :show, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delegate can view a backup" do
|
||||||
|
sign_in_delegate
|
||||||
|
get :show, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-tournament owner cannot view a backup" do
|
||||||
|
sign_in_non_owner
|
||||||
|
get :show, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school delegate cannot view a backup" do
|
||||||
|
sign_in_school_delegate
|
||||||
|
get :show, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
# Destroy endpoint tests
|
||||||
|
test "tournament owner can delete a backup" do
|
||||||
|
sign_in_owner
|
||||||
|
assert_difference("TournamentBackup.count", -1) do
|
||||||
|
delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
end
|
||||||
|
assert_redirected_to tournament_tournament_backups_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delegate can delete a backup" do
|
||||||
|
sign_in_delegate
|
||||||
|
assert_difference("TournamentBackup.count", -1) do
|
||||||
|
delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
end
|
||||||
|
assert_redirected_to tournament_tournament_backups_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-tournament owner cannot delete a backup" do
|
||||||
|
sign_in_non_owner
|
||||||
|
assert_no_difference("TournamentBackup.count") do
|
||||||
|
delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
end
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school delegate cannot delete a backup" do
|
||||||
|
sign_in_school_delegate
|
||||||
|
assert_no_difference("TournamentBackup.count") do
|
||||||
|
delete :destroy, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
end
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
# Restore endpoint tests
|
||||||
|
test "tournament owner can restore a backup" do
|
||||||
|
sign_in_owner
|
||||||
|
post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
assert_redirected_to tournament_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delegate can restore a backup" do
|
||||||
|
sign_in_delegate
|
||||||
|
post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
assert_redirected_to tournament_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-tournament owner cannot restore a backup" do
|
||||||
|
sign_in_non_owner
|
||||||
|
post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school delegate cannot restore a backup" do
|
||||||
|
sign_in_school_delegate
|
||||||
|
post :restore, params: { tournament_id: @tournament.id, id: @backup.id }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
# Import manual tests
|
||||||
|
test "tournament owner can manually import a backup" do
|
||||||
|
sign_in_owner
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
|
||||||
|
assert_redirected_to tournament_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delegate can manually import a backup" do
|
||||||
|
sign_in_delegate
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
|
||||||
|
assert_redirected_to tournament_path(@tournament)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "non-tournament owner cannot manually import a backup" do
|
||||||
|
sign_in_non_owner
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "school delegate cannot manually import a backup" do
|
||||||
|
sign_in_school_delegate
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: Base64.decode64(@backup.backup_data) } }
|
||||||
|
redirect
|
||||||
|
end
|
||||||
|
|
||||||
|
test "index shows empty list when no backups exist" do
|
||||||
|
@tournament.tournament_backups.destroy_all
|
||||||
|
sign_in_owner
|
||||||
|
get :index, params: { tournament_id: @tournament.id }
|
||||||
|
assert_response :success
|
||||||
|
assert_select 'tbody tr', 0 # Ensure no rows are rendered in the table
|
||||||
|
end
|
||||||
|
|
||||||
|
test "show action for non-existent backup" do
|
||||||
|
sign_in_owner
|
||||||
|
assert_raises(ActiveRecord::RecordNotFound) do
|
||||||
|
get :show, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "destroy action for non-existent backup" do
|
||||||
|
sign_in_owner
|
||||||
|
assert_no_difference("TournamentBackup.count") do
|
||||||
|
assert_raises(ActiveRecord::RecordNotFound) do
|
||||||
|
delete :destroy, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "restore action for non-existent backup" do
|
||||||
|
sign_in_owner
|
||||||
|
assert_raises(ActiveRecord::RecordNotFound) do
|
||||||
|
post :restore, params: { tournament_id: @tournament.id, id: 9999 } # Use a non-existent backup ID
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "manual import with blank input" do
|
||||||
|
sign_in_owner
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: '' } }
|
||||||
|
assert_redirected_to tournament_tournament_backups_path(@tournament)
|
||||||
|
assert_equal 'Import text cannot be blank.', flash[:alert]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "manual import restores associations" do
|
||||||
|
schools_count = @tournament.schools.count
|
||||||
|
wrestlers_count = @tournament.wrestlers.count
|
||||||
|
matches_count = @tournament.matches.count
|
||||||
|
sign_in_owner
|
||||||
|
valid_backup_data = Base64.decode64(@backup.backup_data)
|
||||||
|
post :import_manual, params: { tournament_id: @tournament.id, tournament: { import_text: valid_backup_data } }
|
||||||
|
|
||||||
|
@tournament.reload
|
||||||
|
assert_equal schools_count, @tournament.schools.count
|
||||||
|
assert_equal wrestlers_count, @tournament.wrestlers.count
|
||||||
|
assert_equal matches_count, @tournament.matches.count
|
||||||
|
end
|
||||||
|
end
|
||||||
11
test/fixtures/tournament_backups.yml
vendored
Normal file
11
test/fixtures/tournament_backups.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
|
||||||
|
|
||||||
|
# This model initially had no columns defined. If you add columns to the
|
||||||
|
# model remove the "{}" from the fixture names and add the columns immediately
|
||||||
|
# below each fixture, per the syntax in the comments below
|
||||||
|
#
|
||||||
|
# one: {}
|
||||||
|
# column: value
|
||||||
|
#
|
||||||
|
# two: {}
|
||||||
|
# column: value
|
||||||
7
test/models/tournament_backup_test.rb
Normal file
7
test/models/tournament_backup_test.rb
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class TournamentBackupTest < ActiveSupport::TestCase
|
||||||
|
# test "the truth" do
|
||||||
|
# assert true
|
||||||
|
# end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user