This commit is contained in:
cedrick2711
2026-08-25 14:03:07 -04:00
191 changed files with 28790 additions and 5066 deletions
+67 -45
View File
@@ -19,17 +19,16 @@ from app.models import (
Admin, User, Tryout, OrgTeam, TeamPlayer, TeamMatch,
TeamMatchParticipant, AppSettings, BackupRecord, AuditLog,
)
from app.supporting_scripts import backup as backup_helper
from app.supporting_scripts.backup import (
BACKUP_DIR, BackupError, backup_database, backup_documents,
create_backup_dir, parse_database_url, verify_backup,
)
admin_bp = Blueprint('admin', __name__, url_prefix='/admin')
def require_admin():
"""Return True if current user is an Admin, else flash and redirect.
This returns False for non-admins so the caller can stop processing,
but the redirect/abort should be handled by the caller.
"""
"""Return True if current user is an Admin, else flash and redirect."""
if isinstance(current_user, Admin):
return True
flash('Only the president can access the admin panel.', 'danger')
@@ -96,6 +95,27 @@ def dashboard():
# Backups
# ---------------------------------------------------------------------------
def _create_backup_record(backup_type='manual', notes=None):
"""Run a database backup and return a BackupRecord, or raise BackupError."""
create_backup_dir()
conn = parse_database_url(os.getenv('DATABASE_URL'))
file_path = backup_database(conn)
size = os.path.getsize(file_path) if os.path.exists(file_path) else 0
filename = os.path.basename(file_path)
record = BackupRecord(
filename=filename,
file_path=file_path,
size_bytes=size,
backup_type=backup_type,
notes=notes,
created_by_id=current_user.id,
)
db.session.add(record)
db.session.commit()
return record
@admin_bp.route('/backup/create', methods=['POST'])
@login_required
def create_backup():
@@ -105,25 +125,14 @@ def create_backup():
notes = request.form.get('notes', '').strip() or None
try:
result = backup_helper.create_backup(backup_type='manual', notes=notes)
except RuntimeError as e:
record = _create_backup_record(backup_type='manual', notes=notes)
except BackupError as e:
flash(f'Backup failed: {e}', 'danger')
_log('backup_failed', f'Error: {e}')
return redirect(url_for('admin.dashboard'))
record = BackupRecord(
filename=result['filename'],
file_path=result['file_path'],
size_bytes=result['size_bytes'],
backup_type='manual',
notes=notes,
created_by_id=current_user.id,
)
db.session.add(record)
db.session.commit()
_log('backup_created', f'File: {result["filename"]} ({result["size_bytes"]} bytes)')
flash(f'Backup created successfully: {result["filename"]}', 'success')
_log('backup_created', f'File: {record.filename} ({record.size_bytes} bytes)')
flash(f'Backup created successfully: {record.filename}', 'success')
return redirect(url_for('admin.dashboard'))
@@ -171,7 +180,7 @@ def restore_backup(backup_id):
"""Restore a selected backup.
A safety backup of the current state is created first, then the
selected dump is restored via psql.
selected dump is restored via pg_restore.
"""
if not require_admin():
return redirect(url_for('main.dashboard'))
@@ -183,30 +192,50 @@ def restore_backup(backup_id):
# Create a safety backup of the current state before restoring.
try:
safety = backup_helper.create_backup(backup_type='pre_restore', notes='Pre-restore safety backup')
safety_record = BackupRecord(
filename=safety['filename'],
file_path=safety['file_path'],
size_bytes=safety['size_bytes'],
backup_type='auto',
safety = _create_backup_record(
backup_type='pre_restore',
notes='Automatic safety backup before restoring ' + record.filename,
created_by_id=current_user.id,
)
db.session.add(safety_record)
db.session.commit()
except RuntimeError as e:
except BackupError as e:
flash(f'Could not create safety backup, restore aborted: {e}', 'danger')
return redirect(url_for('admin.dashboard'))
# Restore using pg_restore
import subprocess
from app.supporting_scripts.backup import (
PG_RESTORE, dump_environment, parse_database_url,
)
conn = parse_database_url(os.getenv('DATABASE_URL'))
cmd = [
PG_RESTORE,
'--host', conn['host'],
'--port', conn['port'],
'--username', conn['user'],
'--dbname', conn['dbname'],
'--clean', '--if-exists', '--no-owner',
record.file_path,
]
try:
backup_helper.restore_backup(record.file_path)
except RuntimeError as e:
result = subprocess.run(
cmd,
env=dump_environment(conn),
capture_output=True,
text=True,
timeout=900,
)
if result.returncode != 0:
raise RuntimeError(result.stderr.strip() or 'pg_restore failed')
except (subprocess.TimeoutExpired, FileNotFoundError, RuntimeError) as e:
flash(f'Restore failed: {e}', 'danger')
_log('backup_restore_failed', f'File: {record.filename}, Error: {e}')
return redirect(url_for('admin.dashboard'))
_log('backup_restored', f'File: {record.filename}')
flash(f'Backup {record.filename} restored successfully. The database has been rolled back.', 'success')
flash(
f'Backup {record.filename} restored successfully. The database has been rolled back.',
'success',
)
return redirect(url_for('admin.dashboard'))
@@ -322,18 +351,11 @@ def wipe_teams():
# Safety backup before destructive operation
try:
safety = backup_helper.create_backup(backup_type='pre_wipe', notes='Pre-wipe safety backup')
safety_record = BackupRecord(
filename=safety['filename'],
file_path=safety['file_path'],
size_bytes=safety['size_bytes'],
backup_type='auto',
_create_backup_record(
backup_type='pre_wipe',
notes='Automatic safety backup before wiping teams',
created_by_id=current_user.id,
)
db.session.add(safety_record)
db.session.commit()
except RuntimeError as e:
except BackupError as e:
flash(f'Wipe aborted — could not create safety backup: {e}', 'danger')
return redirect(url_for('admin.dashboard'))