1
0
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:
2025-01-04 16:27:52 -05:00
parent 698576dac9
commit 49fbf6735d
17 changed files with 497 additions and 41 deletions

View 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

View File

@@ -1,6 +1,6 @@
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 :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 :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,: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_tournament_errors, only: [:generate_matches]
before_action :check_for_matches, only: [:up_matches,:bracket,:all_brackets]
@@ -10,10 +10,6 @@ class TournamentsController < ApplicationController
end
def export
end
def calculate_team_scores
respond_to do |format|
if @tournament.calculate_all_team_scores
@@ -23,16 +19,6 @@ class TournamentsController < ApplicationController
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
@wrestler = Wrestler.find(params[:wrestler][:originalId])
respond_to do |format|

View File

@@ -8,6 +8,7 @@ class Tournament < ApplicationRecord
has_many :matches, dependent: :destroy
has_many :delegates, class_name: "TournamentDelegate"
has_many :mat_assignment_rules, dependent: :destroy
has_many :tournament_backups, dependent: :destroy
validates :date, :name, :tournament_type, :address, :director, :director_email , presence: true
@@ -227,5 +228,9 @@ class Tournament < ApplicationRecord
end
end
end
def create_backup()
TournamentBackupService.new(self, "Manual backup").create_backup
end
end

View File

@@ -0,0 +1,5 @@
class TournamentBackup < ApplicationRecord
belongs_to :tournament
validates :backup_data, presence: true
end

View File

@@ -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

View File

@@ -1,11 +1,11 @@
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
##### and this tournament id
def initialize(tournament, import_json)
def initialize(tournament, backup)
@tournament = tournament
@import_data = JSON.parse(import_json)
@import_data = JSON.parse(Base64.decode64(backup.backup_data))
end
def import
@@ -26,6 +26,7 @@ class WrestlingdevImporter
end
def destroy_all
@tournament.mat_assignment_rules.destroy_all
@tournament.mats.destroy_all
@tournament.matches.destroy_all
@tournament.schools.each do |school|
@@ -42,6 +43,8 @@ class WrestlingdevImporter
parse_mats(@import_data["tournament"]["mats"])
parse_wrestlers(@import_data["tournament"]["wrestlers"])
parse_matches(@import_data["tournament"]["matches"])
puts "Parsing mat assignment rules"
parse_mat_assignment_rules(@import_data["tournament"]["mat_assignment_rules"])
end
def parse_tournament(attributes)
@@ -70,6 +73,34 @@ class WrestlingdevImporter
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)
wrestlers.each do |wrestler_attributes|
school = School.find_by(name: wrestler_attributes["school"]["name"], tournament_id: @tournament.id)

View File

@@ -33,6 +33,7 @@
<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 "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>
<% if can? :destroy, @tournament %>
<li><%= link_to "Tournament Delegation" , "/tournaments/#{@tournament.id}/delegate" %></li>

View 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 %>

View File

@@ -0,0 +1 @@
<%= JSON.pretty_generate(JSON.parse(Base64.decode64(@tournament_backup.backup_data))) %>

View File

@@ -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 %>

View File

@@ -119,7 +119,7 @@
</table>
<% end %>
<% if can? :manage, @tournament %>
<br>
<br><br>
<h3>Background Jobs</h3>
<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">
@@ -148,9 +148,4 @@
</tbody>
</table>
<% end %>
<br>
<br>
<%= render 'import_form' %>
<br><br>