remodulation du projet et des classes
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# supporting scripts package
|
||||
@@ -0,0 +1,184 @@
|
||||
"""Database backup script for the Team Tryouts application.
|
||||
|
||||
This module provides a simple backup mechanism for the SQLite database
|
||||
and uploaded contract documents. Designed to be run as a scheduled task
|
||||
(Windows Task Scheduler) or cron job.
|
||||
|
||||
Usage:
|
||||
python backup.py
|
||||
|
||||
Configuration via environment variables:
|
||||
BACKUP_DIR: Directory to store backups (default: ./backups)
|
||||
BACKUP_RETENTION_DAYS: Number of days to keep backups (default: 30)
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR = os.getenv('BACKUP_DIR', os.path.join(os.getcwd(), 'backups'))
|
||||
BACKUP_RETENTION_DAYS = int(os.getenv('BACKUP_RETENTION_DAYS', 30))
|
||||
DATABASE_PATH = os.getenv('DATABASE_PATH', os.path.join(os.getcwd(), 'instance', 'team_tryouts.db'))
|
||||
DOCUMENTS_DIR = os.path.join(os.getcwd(), 'documents')
|
||||
|
||||
|
||||
def create_backup_dir():
|
||||
"""Create the backup directory if it doesn't exist."""
|
||||
os.makedirs(BACKUP_DIR, exist_ok=True)
|
||||
|
||||
|
||||
def backup_database():
|
||||
"""Backup the SQLite database using sqlite3's built-in backup API.
|
||||
|
||||
Returns:
|
||||
str: Path to the created backup file, or None if failed.
|
||||
"""
|
||||
if not os.path.exists(DATABASE_PATH):
|
||||
print(f'[WARNING] Database not found at {DATABASE_PATH}. Skipping database backup.')
|
||||
return None
|
||||
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
backup_filename = f'db_backup_{timestamp}.db'
|
||||
backup_path = os.path.join(BACKUP_DIR, backup_filename)
|
||||
|
||||
try:
|
||||
source = sqlite3.connect(DATABASE_PATH)
|
||||
destination = sqlite3.connect(backup_path)
|
||||
source.backup(destination)
|
||||
source.close()
|
||||
destination.close()
|
||||
print(f'[OK] Database backed up to: {backup_path}')
|
||||
return backup_path
|
||||
except Exception as e:
|
||||
print(f'[ERROR] Database backup failed: {e}')
|
||||
return None
|
||||
|
||||
|
||||
def backup_documents():
|
||||
"""Backup the uploaded contract documents directory.
|
||||
|
||||
Returns:
|
||||
str: Path to the created archive, or None if no documents exist.
|
||||
"""
|
||||
if not os.path.exists(DOCUMENTS_DIR):
|
||||
print('[INFO] No documents directory found. Skipping document backup.')
|
||||
return None
|
||||
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
archive_basename = f'documents_backup_{timestamp}'
|
||||
archive_path = os.path.join(BACKUP_DIR, archive_basename)
|
||||
|
||||
try:
|
||||
shutil.make_archive(archive_path, 'zip', DOCUMENTS_DIR)
|
||||
zip_path = f'{archive_path}.zip'
|
||||
print(f'[OK] Documents backed up to: {zip_path}')
|
||||
return zip_path
|
||||
except Exception as e:
|
||||
print(f'[ERROR] Document backup failed: {e}')
|
||||
return None
|
||||
|
||||
|
||||
def cleanup_old_backups():
|
||||
"""Remove backup files older than BACKUP_RETENTION_DAYS."""
|
||||
if not os.path.exists(BACKUP_DIR):
|
||||
return
|
||||
|
||||
cutoff = datetime.now() - timedelta(days=BACKUP_RETENTION_DAYS)
|
||||
removed_count = 0
|
||||
|
||||
for filename in os.listdir(BACKUP_DIR):
|
||||
file_path = os.path.join(BACKUP_DIR, filename)
|
||||
if os.path.isfile(file_path):
|
||||
file_time = datetime.fromtimestamp(os.path.getmtime(file_path))
|
||||
if file_time < cutoff:
|
||||
try:
|
||||
os.remove(file_path)
|
||||
removed_count += 1
|
||||
print(f'[CLEANUP] Removed old backup: {filename}')
|
||||
except OSError as e:
|
||||
print(f'[WARNING] Could not remove {filename}: {e}')
|
||||
|
||||
if removed_count > 0:
|
||||
print(f'[CLEANUP] Removed {removed_count} old backup(s).')
|
||||
else:
|
||||
print('[CLEANUP] No old backups to remove.')
|
||||
|
||||
|
||||
def verify_backup(backup_path):
|
||||
"""Verify a database backup by running a quick integrity check.
|
||||
|
||||
Args:
|
||||
backup_path: Path to the backup file to verify.
|
||||
|
||||
Returns:
|
||||
bool: True if backup is valid, False otherwise.
|
||||
"""
|
||||
if not backup_path or not os.path.exists(backup_path):
|
||||
return False
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(backup_path)
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('PRAGMA integrity_check')
|
||||
result = cursor.fetchone()
|
||||
conn.close()
|
||||
is_valid = result[0] == 'ok'
|
||||
if is_valid:
|
||||
print(f'[OK] Backup integrity verified: {backup_path}')
|
||||
else:
|
||||
print(f'[ERROR] Backup integrity check failed: {backup_path} - {result[0]}')
|
||||
return is_valid
|
||||
except Exception as e:
|
||||
print(f'[ERROR] Backup verification failed: {e}')
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
"""Run the full backup process.
|
||||
|
||||
Steps:
|
||||
1. Create backup directory
|
||||
2. Backup database
|
||||
3. Backup documents (if any)
|
||||
4. Verify database backup
|
||||
5. Clean up old backups
|
||||
|
||||
Returns:
|
||||
int: 0 on success, 1 on failure.
|
||||
"""
|
||||
print(f'=== Team Tryouts Backup ===')
|
||||
print(f'Started at: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
print(f'Backup directory: {BACKUP_DIR}')
|
||||
print(f'Retention period: {BACKUP_RETENTION_DAYS} days')
|
||||
print()
|
||||
|
||||
create_backup_dir()
|
||||
|
||||
# 1. Backup database
|
||||
db_backup_path = backup_database()
|
||||
success = True
|
||||
|
||||
# 2. Verify database backup
|
||||
if db_backup_path:
|
||||
if not verify_backup(db_backup_path):
|
||||
success = False
|
||||
|
||||
# 3. Backup documents
|
||||
backup_documents()
|
||||
|
||||
# 4. Cleanup old backups
|
||||
cleanup_old_backups()
|
||||
|
||||
print()
|
||||
if success:
|
||||
print('=== Backup completed successfully ===')
|
||||
else:
|
||||
print('=== Backup completed with warnings ===')
|
||||
|
||||
return 0 if success else 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
||||
@@ -0,0 +1,129 @@
|
||||
"""Development HTTPS server for testing production settings locally.
|
||||
|
||||
Generates a self-signed certificate (if not present) and runs the
|
||||
application via Waitress wrapped in a TLS socket. This simulates the
|
||||
production environment where Nginx handles TLS termination.
|
||||
|
||||
Usage:
|
||||
python run_https.py
|
||||
|
||||
The server will listen on https://localhost:8443
|
||||
Accept the self-signed certificate warning in your browser to proceed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import ssl
|
||||
import sys
|
||||
from waitress.server import create_server
|
||||
from app.app import create_app
|
||||
|
||||
CERT_FILE = 'certs/localhost.pem'
|
||||
KEY_FILE = 'certs/localhost-key.pem'
|
||||
|
||||
|
||||
def generate_self_signed_cert():
|
||||
"""Generate a self-signed certificate for local HTTPS testing.
|
||||
|
||||
Uses OpenSSL to create a key and certificate valid for 365 days.
|
||||
Skips generation if certificate files already exist.
|
||||
"""
|
||||
if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE):
|
||||
print('[OK] Self-signed certificate already exists.')
|
||||
return
|
||||
|
||||
os.makedirs('certs', exist_ok=True)
|
||||
|
||||
print('[INFO] Generating self-signed certificate for localhost...')
|
||||
try:
|
||||
subprocess.run([
|
||||
'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
|
||||
'-keyout', KEY_FILE,
|
||||
'-out', CERT_FILE,
|
||||
'-days', '365',
|
||||
'-nodes',
|
||||
'-subj', '/CN=localhost'
|
||||
], check=True, capture_output=True)
|
||||
print('[OK] Certificate generated: certs/localhost.pem')
|
||||
except FileNotFoundError:
|
||||
print('[ERROR] OpenSSL not found. Install OpenSSL or use:')
|
||||
print(' winget install OpenSSL.OpenSSL')
|
||||
print(' OR download from https://slproweb.com/products/Win32OpenSSL.html')
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f'[ERROR] Certificate generation failed: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the HTTPS development server."""
|
||||
port = int(os.getenv('HTTPS_PORT', 8443))
|
||||
host = os.getenv('HOST', '127.0.0.1')
|
||||
|
||||
# Ensure certificate exists
|
||||
generate_self_signed_cert()
|
||||
|
||||
# Create the Flask application
|
||||
app = create_app()
|
||||
|
||||
# Create SSL context
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(CERT_FILE, KEY_FILE)
|
||||
|
||||
# Create a TCP socket, wrap it with TLS, then pass to Waitress
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((host, port))
|
||||
sock.listen(5)
|
||||
|
||||
# Wrap the socket with TLS
|
||||
ssl_sock = context.wrap_socket(sock, server_side=True)
|
||||
|
||||
# =====================================================================
|
||||
# WSGI Middleware: Tell Flask the connection is HTTPS
|
||||
#
|
||||
# Waitress doesn't know the underlying socket is TLS, so Flask sees
|
||||
# wsgi.url_scheme = "http". Without this middleware, force_https()
|
||||
# would cause an infinite redirect loop (ERR_TOO_MANY_REDIRECTS).
|
||||
# =====================================================================
|
||||
class ForceHTTPSMiddleware:
|
||||
"""WSGI middleware that sets url_scheme to 'https'.
|
||||
|
||||
Since we're wrapping the TCP socket with SSL before passing it
|
||||
to Waitress, Flask's request.is_secure returns False because
|
||||
Waitress reports wsgi.url_scheme='http'. This middleware fixes
|
||||
that so Flask correctly identifies the connection as HTTPS.
|
||||
"""
|
||||
|
||||
def __init__(self, wsgi_app):
|
||||
self.wsgi_app = wsgi_app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
environ['wsgi.url_scheme'] = 'https'
|
||||
environ['HTTPS'] = 'on'
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
# Wrap the Flask app with the HTTPS middleware
|
||||
app.wsgi_app = ForceHTTPSMiddleware(app.wsgi_app)
|
||||
|
||||
print(f'\n╔══════════════════════════════════════════════════════╗')
|
||||
print(f'║ TEAM TRYOUTS - Development HTTPS Server ║')
|
||||
print(f'╠══════════════════════════════════════════════════════╣')
|
||||
print(f'║ URL: https://{host}:{port} ║')
|
||||
print(f'║ Cert: self-signed (accept browser warning) ║')
|
||||
print(f'║ Press Ctrl+C to stop ║')
|
||||
print(f'╚══════════════════════════════════════════════════════╝\n')
|
||||
|
||||
# Create Waitress server with the SSL-wrapped socket
|
||||
server = create_server(
|
||||
app,
|
||||
sockets=[ssl_sock],
|
||||
threads=4,
|
||||
channel_timeout=30,
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,372 @@
|
||||
"""Security validation script for the Team Tryouts application.
|
||||
|
||||
This script performs pre-deployment security checks to validate:
|
||||
- HTTP security headers
|
||||
- Cookie security attributes
|
||||
- Debug mode status
|
||||
- HTTPS configuration
|
||||
- Dependency vulnerabilities
|
||||
- Database connectivity
|
||||
|
||||
Usage:
|
||||
python security_scan.py [--url http://localhost:5000]
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import subprocess
|
||||
import urllib.request
|
||||
import ssl
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
def check_environment():
|
||||
"""Check required environment variables are set.
|
||||
|
||||
Returns:
|
||||
bool: True if all critical variables are set.
|
||||
"""
|
||||
print('=' * 60)
|
||||
print('1. ENVIRONMENT VARIABLES CHECK')
|
||||
print('=' * 60)
|
||||
|
||||
critical_vars = ['SECRET_KEY']
|
||||
recommended_vars = ['DATABASE_URL', 'CORS_ALLOWED_ORIGINS']
|
||||
all_ok = True
|
||||
|
||||
for var in critical_vars:
|
||||
value = os.getenv(var)
|
||||
if value:
|
||||
# Check SECRET_KEY is not a default/weak value
|
||||
if var == 'SECRET_KEY' and len(value) < 32:
|
||||
print(f'[WARN] {var} is set but too short (less than 32 chars)')
|
||||
all_ok = False
|
||||
else:
|
||||
print(f'[OK] {var} is set')
|
||||
else:
|
||||
print(f'[FAIL] {var} is not set!')
|
||||
all_ok = False
|
||||
|
||||
for var in recommended_vars:
|
||||
value = os.getenv(var)
|
||||
if value:
|
||||
print(f'[OK] {var} is set')
|
||||
else:
|
||||
print(f'[INFO] {var} is not set (using default)')
|
||||
|
||||
# Check FLASK_DEBUG
|
||||
debug = os.getenv('FLASK_DEBUG', 'false').lower()
|
||||
if debug == 'true':
|
||||
print('[WARN] FLASK_DEBUG is enabled! Should be disabled in production.')
|
||||
else:
|
||||
print('[OK] FLASK_DEBUG is disabled')
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def check_https_headers(url):
|
||||
"""Check HTTP security headers from a running application.
|
||||
|
||||
Args:
|
||||
url: The base URL of the application to check.
|
||||
|
||||
Returns:
|
||||
bool: True if all critical headers are present.
|
||||
"""
|
||||
print('\n' + '=' * 60)
|
||||
print('2. HTTP SECURITY HEADERS CHECK')
|
||||
print('=' * 60)
|
||||
|
||||
required_headers = {
|
||||
'Strict-Transport-Security': 'HSTS enabled',
|
||||
'X-Content-Type-Options': 'Prevents MIME sniffing',
|
||||
'X-Frame-Options': 'Prevents clickjacking',
|
||||
'Content-Security-Policy': 'CSP configured',
|
||||
'Referrer-Policy': 'Referrer control',
|
||||
'Permissions-Policy': 'Permissions control',
|
||||
'Cross-Origin-Opener-Policy': 'Cross-origin isolation',
|
||||
}
|
||||
|
||||
all_ok = True
|
||||
|
||||
try:
|
||||
# Create a context that doesn't verify SSL (for local testing)
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
|
||||
req = urllib.request.Request(url, method='HEAD')
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, context=ctx, timeout=10) as response:
|
||||
headers = response.headers
|
||||
status = response.status
|
||||
|
||||
print(f'[INFO] Response status: {status}')
|
||||
|
||||
for header, description in required_headers.items():
|
||||
if header in headers:
|
||||
print(f'[OK] {header}: {description}')
|
||||
else:
|
||||
print(f'[FAIL] {header} is missing: {description}')
|
||||
all_ok = False
|
||||
|
||||
# Check cookie attributes if any set-cookie headers exist
|
||||
if 'Set-Cookie' in headers:
|
||||
cookie = headers['Set-Cookie']
|
||||
if 'Secure' in cookie:
|
||||
print('[OK] Cookies have Secure flag')
|
||||
else:
|
||||
print('[WARN] Cookies missing Secure flag')
|
||||
all_ok = False
|
||||
|
||||
if 'HttpOnly' in cookie:
|
||||
print('[OK] Cookies have HttpOnly flag')
|
||||
else:
|
||||
print('[WARN] Cookies missing HttpOnly flag')
|
||||
all_ok = False
|
||||
|
||||
if 'SameSite' in cookie:
|
||||
print(f'[OK] Cookies have SameSite={cookie.split("SameSite=")[1].split(";")[0] if "SameSite=" in cookie else "?"}')
|
||||
else:
|
||||
print('[WARN] Cookies missing SameSite attribute')
|
||||
all_ok = False
|
||||
else:
|
||||
print('[INFO] No Set-Cookie headers in response')
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f'[INFO] Got HTTP {e.code} (may need authentication)')
|
||||
# Still check headers even on error responses
|
||||
for header, description in required_headers.items():
|
||||
if header in e.headers:
|
||||
print(f'[OK] {header}: {description}')
|
||||
else:
|
||||
print(f'[FAIL] {header} is missing: {description}')
|
||||
all_ok = False
|
||||
|
||||
except urllib.error.URLError as e:
|
||||
print(f'[SKIP] Cannot connect to {url}: {e.reason}')
|
||||
print('[SKIP] Run with --url <application_url> to check headers')
|
||||
return True # Not a failure, just can't check
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def check_dependencies():
|
||||
"""Run pip-audit to check for known vulnerabilities.
|
||||
|
||||
Returns:
|
||||
bool: True if no critical vulnerabilities found.
|
||||
"""
|
||||
print('\n' + '=' * 60)
|
||||
print('3. DEPENDENCY VULNERABILITY SCAN')
|
||||
print('=' * 60)
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-m', 'pip_audit', '--format', 'json'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
print('[OK] No known vulnerabilities found')
|
||||
return True
|
||||
else:
|
||||
try:
|
||||
data = json.loads(result.stdout)
|
||||
vulns = data.get('dependencies', [])
|
||||
if vulns:
|
||||
for vuln in vulns:
|
||||
print(f'[FAIL] {vuln["name"]}=={vuln["version"]}: {vuln.get("description", "Vulnerability found")}')
|
||||
return False
|
||||
else:
|
||||
print('[OK] No vulnerabilities found')
|
||||
return True
|
||||
except json.JSONDecodeError:
|
||||
if result.stdout:
|
||||
print(f'[INFO] {result.stdout.strip()}')
|
||||
if result.stderr:
|
||||
print(f'[WARN] {result.stderr.strip()}')
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
print('[SKIP] pip-audit not installed. Run: pip install pip-audit')
|
||||
return True
|
||||
except subprocess.TimeoutExpired:
|
||||
print('[WARN] pip-audit timed out')
|
||||
return True
|
||||
|
||||
|
||||
def check_file_permissions():
|
||||
"""Check for common security issues in the project structure.
|
||||
|
||||
Returns:
|
||||
bool: True if no critical issues found.
|
||||
"""
|
||||
print('\n' + '=' * 60)
|
||||
print('4. PROJECT FILES CHECK')
|
||||
print('=' * 60)
|
||||
|
||||
all_ok = True
|
||||
|
||||
# Check .gitignore exists and contains important patterns
|
||||
gitignore_path = os.path.join(os.getcwd(), '.gitignore')
|
||||
if os.path.exists(gitignore_path):
|
||||
required_patterns = ['.env', 'instance/', '*.db', '*.log']
|
||||
with open(gitignore_path, 'r') as f:
|
||||
content = f.read()
|
||||
|
||||
for pattern in required_patterns:
|
||||
if pattern in content:
|
||||
print(f'[OK] .gitignore contains: {pattern}')
|
||||
else:
|
||||
print(f'[WARN] .gitignore missing: {pattern}')
|
||||
all_ok = False
|
||||
else:
|
||||
print('[FAIL] .gitignore file not found!')
|
||||
all_ok = False
|
||||
|
||||
# Check for .env in working directory (should NOT be committed)
|
||||
env_path = os.path.join(os.getcwd(), '.env')
|
||||
if os.path.exists(env_path):
|
||||
print('[INFO] .env file exists (ensure it is NOT committed)')
|
||||
else:
|
||||
print('[WARN] No .env file found')
|
||||
|
||||
# Check for leftover .pyc or __pycache__
|
||||
pycache_count = 0
|
||||
for root, dirs, files in os.walk(os.getcwd()):
|
||||
if '__pycache__' in dirs:
|
||||
pycache_count += 1
|
||||
for f in files:
|
||||
if f.endswith('.pyc'):
|
||||
pycache_count += 1
|
||||
if pycache_count == 0:
|
||||
print('[OK] No __pycache__ or .pyc files found')
|
||||
else:
|
||||
print(f'[INFO] Found {pycache_count} cache files/dirs (should be in .gitignore)')
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def check_flask_config():
|
||||
"""Check Flask application configuration for security.
|
||||
|
||||
Returns:
|
||||
bool: True if configuration looks secure.
|
||||
"""
|
||||
print('\n' + '=' * 60)
|
||||
print('5. FLASK CONFIGURATION CHECK')
|
||||
print('=' * 60)
|
||||
|
||||
all_ok = True
|
||||
|
||||
try:
|
||||
from app.app import create_app
|
||||
app = create_app()
|
||||
|
||||
# Check session cookie settings
|
||||
cookie_checks = [
|
||||
('SESSION_COOKIE_SECURE', True, 'Secure cookies'),
|
||||
('SESSION_COOKIE_HTTPONLY', True, 'HttpOnly cookies'),
|
||||
('PERMANENT_SESSION_LIFETIME', 3600, 'Session timeout'),
|
||||
]
|
||||
|
||||
for config_key, expected, description in cookie_checks:
|
||||
value = app.config.get(config_key)
|
||||
if config_key == 'PERMANENT_SESSION_LIFETIME':
|
||||
if value and value <= 3600:
|
||||
print(f'[OK] {description}: {value}s')
|
||||
else:
|
||||
print(f'[WARN] {description}: {value}s (should be <= 1 hour)')
|
||||
all_ok = False
|
||||
elif value == expected:
|
||||
print(f'[OK] {description}: enabled')
|
||||
else:
|
||||
print(f'[FAIL] {description}: {value}')
|
||||
all_ok = False
|
||||
|
||||
# Check MAX_CONTENT_LENGTH
|
||||
max_content = app.config.get('MAX_CONTENT_LENGTH')
|
||||
if max_content:
|
||||
mb = max_content / (1024 * 1024)
|
||||
print(f'[OK] MAX_CONTENT_LENGTH: {mb}MB')
|
||||
else:
|
||||
print('[WARN] MAX_CONTENT_LENGTH not set (unlimited uploads)')
|
||||
all_ok = False
|
||||
|
||||
# Check CSRF
|
||||
csrf_enabled = app.config.get('WTF_CSRF_ENABLED')
|
||||
if csrf_enabled:
|
||||
print('[OK] CSRF protection: enabled')
|
||||
else:
|
||||
print('[FAIL] CSRF protection: disabled')
|
||||
all_ok = False
|
||||
|
||||
# Check if app is in DEBUG mode
|
||||
if app.debug:
|
||||
print('[FAIL] DEBUG mode is enabled!')
|
||||
all_ok = False
|
||||
else:
|
||||
print('[OK] DEBUG mode: disabled')
|
||||
|
||||
except Exception as e:
|
||||
print(f'[SKIP] Cannot check Flask config: {e}')
|
||||
|
||||
return all_ok
|
||||
|
||||
|
||||
def main():
|
||||
"""Run all security checks and produce a summary report.
|
||||
|
||||
Returns:
|
||||
int: 0 if all checks pass, 1 if any fail.
|
||||
"""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description='Security validation scanner')
|
||||
parser.add_argument('--url', default='http://localhost:5000',
|
||||
help='Application URL to check headers (default: http://localhost:5000)')
|
||||
args = parser.parse_args()
|
||||
|
||||
print('╔══════════════════════════════════════════════════════════╗')
|
||||
print('║ TEAM TRYOUTS - SECURITY VALIDATION SCANNER ║')
|
||||
print('╠══════════════════════════════════════════════════════════╣')
|
||||
print(f'║ Time: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
|
||||
print('╚══════════════════════════════════════════════════════════╝')
|
||||
|
||||
checks = [
|
||||
check_environment,
|
||||
lambda: check_https_headers(args.url),
|
||||
check_dependencies,
|
||||
check_file_permissions,
|
||||
check_flask_config,
|
||||
]
|
||||
|
||||
results = []
|
||||
for check in checks:
|
||||
results.append(check())
|
||||
|
||||
print('\n' + '=' * 60)
|
||||
print('SUMMARY')
|
||||
print('=' * 60)
|
||||
|
||||
passed = sum(1 for r in results if r)
|
||||
failed = sum(1 for r in results if not r)
|
||||
total = len(results)
|
||||
|
||||
print(f'Passed: {passed}/{total}')
|
||||
print(f'Failed: {failed}/{total}')
|
||||
|
||||
if failed == 0:
|
||||
print('\n[OK] All security checks passed!')
|
||||
return 0
|
||||
else:
|
||||
print(f'\n[WARN] {failed} check(s) failed. Review the output above.')
|
||||
return 1
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,460 @@
|
||||
"""Database seeding script for Team Tryouts application.
|
||||
|
||||
Creates sample data using the polymorphic User subclasses:
|
||||
Admin, Manager, Coach, Player, Scout.
|
||||
"""
|
||||
|
||||
from app.extensions import db, hash_password
|
||||
from app.models import (
|
||||
Admin, Manager, Coach, Player, Scout,
|
||||
Tryout, TryoutRegistration, Evaluation, Team, TeamMember,
|
||||
OrgTeam, TeamPlayer,
|
||||
PlayerDisponibility, CoachAvailability,
|
||||
UserGamertag, TeamNote, PersonalNote,
|
||||
Match, MatchParticipant,
|
||||
BaseAvailability, BaseMatch, BaseParticipant,
|
||||
)
|
||||
from datetime import datetime, timedelta, time
|
||||
import random
|
||||
|
||||
|
||||
def seed_database():
|
||||
"""Seed the database with sample data for development and testing."""
|
||||
|
||||
# Clear existing data (order matters for FK constraints)
|
||||
for table in ['player_disponibilities', 'coach_availabilities',
|
||||
'match_participants', 'team_match_participants',
|
||||
'matches', 'team_matches',
|
||||
'team_players', 'team_members', 'teams',
|
||||
'evaluations', 'tryout_registrations', 'tryouts',
|
||||
'org_team_coaches', 'org_team_managers',
|
||||
'org_teams', 'user_gamertags',
|
||||
'personal_notes', 'team_notes',
|
||||
'one_on_one_requests',
|
||||
'users']:
|
||||
db.session.execute(db.text(f'DELETE FROM {table}'))
|
||||
db.session.commit()
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Staff users (Admin, Manager, Coach, Scout)
|
||||
# -----------------------------------------------------------------------
|
||||
admin = Admin(
|
||||
username='admin', password_hash=hash_password('password'),
|
||||
role='admin', full_name='Sarah Johnson',
|
||||
email='[email protected]', phone='555-0101')
|
||||
db.session.add(admin)
|
||||
|
||||
manager1 = Manager(
|
||||
username='manager1', password_hash=hash_password('password'),
|
||||
role='manager', full_name='Mike Williams',
|
||||
email='[email protected]', phone='555-0102')
|
||||
db.session.add(manager1)
|
||||
|
||||
manager2 = Manager(
|
||||
username='manager2', password_hash=hash_password('password'),
|
||||
role='manager', full_name='Emily Davis',
|
||||
email='[email protected]', phone='555-0103')
|
||||
db.session.add(manager2)
|
||||
|
||||
coach1 = Coach(
|
||||
username='coach1', password_hash=hash_password('password'),
|
||||
role='coach', full_name='Coach Thompson',
|
||||
email='[email protected]', phone='555-0104',
|
||||
discord_user_id='484107446298738689')
|
||||
db.session.add(coach1)
|
||||
|
||||
coach2 = Coach(
|
||||
username='coach2', password_hash=hash_password('password'),
|
||||
role='coach', full_name='Coach Martinez',
|
||||
email='[email protected]', phone='555-0105')
|
||||
db.session.add(coach2)
|
||||
|
||||
coach3 = Coach(
|
||||
username='coach3', password_hash=hash_password('password'),
|
||||
role='coach', full_name='Coach Anderson',
|
||||
email='[email protected]', phone='555-0106')
|
||||
db.session.add(coach3)
|
||||
|
||||
scout = Scout(
|
||||
username='scout1', password_hash=hash_password('password'),
|
||||
role='scout', full_name='Alex Rivera',
|
||||
email='[email protected]', phone='555-0107')
|
||||
db.session.add(scout)
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Players
|
||||
# -----------------------------------------------------------------------
|
||||
player_data = [
|
||||
{'username': 'jplayer1', 'full_name': 'nordjan', 'email': '[email protected]',
|
||||
'games': 'Valorant, Counter-Strike 2, Rainbow Six Siege, Rocket League, Overwatch 2',
|
||||
'discord_username': 'nordjan', 'discord_user_id': '484107446298738689',
|
||||
'league_os_profile': 'https://leagueos.gg/player/nordjan'},
|
||||
{'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': '[email protected]',
|
||||
'games': 'League of Legends,Valorant',
|
||||
'discord_username': 'EmmaG#4452', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/emmagarcia'},
|
||||
{'username': 'jplayer3', 'full_name': 'Liam Brown', 'email': '[email protected]',
|
||||
'games': 'Apex Legends,Fortnite',
|
||||
'discord_username': 'LiamB#8103', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/liambrown'},
|
||||
{'username': 'jplayer4', 'full_name': 'Sophia Lee', 'email': '[email protected]',
|
||||
'games': 'Overwatch 2,Valorant',
|
||||
'discord_username': 'SophiaL#3327', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/sophialee'},
|
||||
{'username': 'jplayer5', 'full_name': 'Noah Taylor', 'email': '[email protected]',
|
||||
'games': 'Counter-Strike 2,Rainbow Six Siege',
|
||||
'discord_username': 'NoahT#6614', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/noahtaylor'},
|
||||
{'username': 'jplayer6', 'full_name': 'Olivia Martin', 'email': '[email protected]',
|
||||
'games': 'Rocket League,Fortnite',
|
||||
'discord_username': 'OliviaM#2298', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/oliviamartin'},
|
||||
{'username': 'jplayer7', 'full_name': 'Ethan Clark', 'email': '[email protected]',
|
||||
'games': 'Valorant,Apex Legends',
|
||||
'discord_username': 'EthanC#7743', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/ethanclark'},
|
||||
{'username': 'jplayer8', 'full_name': 'Ava White', 'email': '[email protected]',
|
||||
'games': 'League of Legends,Counter-Strike 2',
|
||||
'discord_username': 'AvaW#5561', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/avawhite'},
|
||||
{'username': 'jplayer9', 'full_name': 'Mason Hall', 'email': '[email protected]',
|
||||
'games': 'Call of Duty,Rocket League',
|
||||
'discord_username': 'MasonH#1189', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/masonhall'},
|
||||
{'username': 'jplayer10', 'full_name': 'Isabella Adams', 'email': '[email protected]',
|
||||
'games': 'Overwatch 2,Dota 2',
|
||||
'discord_username': 'IsabellaA#4437', 'discord_user_id': '',
|
||||
'league_os_profile': 'https://leagueos.gg/player/isabellaadams'},
|
||||
]
|
||||
|
||||
players = []
|
||||
for i, data in enumerate(player_data, start=10):
|
||||
p = Player(
|
||||
username=data['username'], password_hash=hash_password('password'),
|
||||
role='player', full_name=data['full_name'],
|
||||
email=data['email'], phone=f'555-01{i:02d}',
|
||||
games=data['games'],
|
||||
discord_username=data['discord_username'],
|
||||
discord_user_id=data['discord_user_id'],
|
||||
league_os_profile=data['league_os_profile'])
|
||||
db.session.add(p)
|
||||
players.append(p)
|
||||
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {7 + 10} users (7 staff + 10 players)")
|
||||
|
||||
# Map for easy reference
|
||||
coaches = [coach1, coach2, coach3]
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Gamertags
|
||||
# -----------------------------------------------------------------------
|
||||
gt_map = [
|
||||
(players[0], 'Valorant', 'nordjan#bad', None),
|
||||
(players[0], 'Counter-Strike 2', 'nordjan', None),
|
||||
(players[0], 'Rainbow Six Siege', 'nordjan', 'Ubisoft'),
|
||||
(players[0], 'Rocket League', 'nordjiano', 'Epic'),
|
||||
(players[0], 'Overwatch 2', 'nordjan', 'PC'),
|
||||
(players[1], 'League of Legends', 'emmagarcia_lol', None),
|
||||
(players[1], 'Valorant', 'emmagarcia_val', None),
|
||||
(players[2], 'Apex Legends', 'liambrown_apex', 'PC'),
|
||||
(players[2], 'Fortnite', 'liambrown_fn', 'PC'),
|
||||
(players[3], 'Overwatch 2', 'sophialee_ow', 'PC'),
|
||||
(players[3], 'Valorant', 'sophialee_val', None),
|
||||
(players[4], 'Counter-Strike 2', 'noahtaylor_cs', None),
|
||||
(players[4], 'Rainbow Six Siege', 'noahtaylor_r6', 'Ubisoft'),
|
||||
(players[5], 'Rocket League', 'oliviamartin_rl', 'Epic'),
|
||||
(players[5], 'Fortnite', 'oliviamartin_fn', 'PC'),
|
||||
(players[6], 'Valorant', 'ethanclark_val', None),
|
||||
(players[6], 'Apex Legends', 'ethanclark_apex', 'PC'),
|
||||
(players[7], 'League of Legends', 'avawhite_lol', None),
|
||||
(players[7], 'Counter-Strike 2', 'avawhite_cs', None),
|
||||
(players[8], 'Call of Duty', 'masonhall_cod', 'PC'),
|
||||
(players[8], 'Rocket League', 'masonhall_rl', 'Epic'),
|
||||
(players[9], 'Overwatch 2', 'isabellaadams_ow', 'PC'),
|
||||
(players[9], 'Dota 2', 'isabellaadams_dota', None),
|
||||
]
|
||||
for p, game, tag, platform in gt_map:
|
||||
db.session.add(UserGamertag(user_id=p.id, game=game, gamertag=tag, platform=platform))
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(gt_map)} gamertags")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Organisation Teams
|
||||
# -----------------------------------------------------------------------
|
||||
org_teams_data = [
|
||||
{'name': 'Rocket League main', 'coach': coaches[0], 'creator': admin},
|
||||
{'name': 'CS2', 'coach': coaches[1], 'creator': admin},
|
||||
{'name': 'Valorant', 'coach': coaches[2], 'creator': admin},
|
||||
{'name': 'Rocket League acad', 'coach': None, 'creator': manager1},
|
||||
]
|
||||
|
||||
org_teams = []
|
||||
for data in org_teams_data:
|
||||
ot = OrgTeam(
|
||||
name=data['name'],
|
||||
coach_id=data['coach'].id if data['coach'] else None,
|
||||
created_by=data['creator'].id)
|
||||
db.session.add(ot)
|
||||
org_teams.append(ot)
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(org_teams)} organisation teams")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Tryouts
|
||||
# -----------------------------------------------------------------------
|
||||
tryouts_data = [
|
||||
{'title': 'Rocket Leauge Tryouts', 'game': 'Rocket League',
|
||||
'date': datetime.utcnow(), 'location': 'En ligne',
|
||||
'description': 'Tryouts for the spring competitive season. All positions welcome.',
|
||||
'status': 'in_progress', 'creator': manager1, 'target_team': org_teams[0]},
|
||||
{'title': 'CS2 Tryouts', 'game': 'Counter-Strike 2',
|
||||
'date': datetime.utcnow() + timedelta(days=4), 'location': 'En ligne',
|
||||
'description': 'Trials for the fall select team. High skill level required.',
|
||||
'status': 'upcoming', 'creator': manager1, 'target_team': org_teams[3]},
|
||||
{'title': 'Valorant Tryouts', 'game': 'Valorant',
|
||||
'date': datetime.utcnow() - timedelta(days=2), 'location': 'En ligne',
|
||||
'description': "Séléction pour l'équipe de Valorant",
|
||||
'status': 'in_progress', 'creator': manager2, 'target_team': org_teams[2]},
|
||||
]
|
||||
|
||||
tryouts = []
|
||||
for data in tryouts_data:
|
||||
t = Tryout(
|
||||
title=data['title'], game=data['game'],
|
||||
date=data['date'].date(),
|
||||
location=data['location'],
|
||||
description=data['description'],
|
||||
status=data['status'], max_players=15,
|
||||
created_by=data['creator'].id,
|
||||
target_org_team_id=data['target_team'].id if data['target_team'] else None)
|
||||
db.session.add(t)
|
||||
tryouts.append(t)
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(tryouts)} tryouts")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Registrations
|
||||
# -----------------------------------------------------------------------
|
||||
reg_assignments = [
|
||||
(tryouts[0], players[:3]),
|
||||
(tryouts[1], players[3:5]),
|
||||
(tryouts[2], players),
|
||||
]
|
||||
regs = []
|
||||
for tryout, plist in reg_assignments:
|
||||
for player in plist:
|
||||
reg = TryoutRegistration(
|
||||
tryout_id=tryout.id, player_id=player.id,
|
||||
status=random.choice(['registered', 'attended', 'attended', 'no_show']))
|
||||
db.session.add(reg)
|
||||
regs.append(reg)
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(regs)} registrations")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Evaluations
|
||||
# -----------------------------------------------------------------------
|
||||
eval_count = 0
|
||||
rl_positions = ['None needed', 'None', 'N/A']
|
||||
for player in players[:8]:
|
||||
for coach in coaches:
|
||||
if random.random() > 0.3:
|
||||
ms = [random.randint(4, 10) for _ in range(9)]
|
||||
overall = round(sum(ms) / 9, 1)
|
||||
db.session.add(Evaluation(
|
||||
tryout_id=tryouts[0].id, player_id=player.id,
|
||||
evaluator_id=coach.id,
|
||||
mecanics_score=ms[0], cohesion_score=ms[1],
|
||||
communication_score=ms[2], gamesense_score=ms[3],
|
||||
versatility_score=ms[4], discipline_score=ms[5],
|
||||
analysis_score=ms[6], sport_ethics_score=ms[7],
|
||||
mental_score=ms[8], overall_score=overall,
|
||||
comments=f"{'Great' if overall > 7 else 'Good'} performance. {'Shows promise.' if overall > 6 else 'Needs improvement in some areas.'}",
|
||||
position_recommendation=random.choice(rl_positions)))
|
||||
eval_count += 1
|
||||
|
||||
val_positions = ['Controller', 'Initiator', 'Duelist', 'Sentinel']
|
||||
for player in players[:6]:
|
||||
for coach in coaches[:2]:
|
||||
ms = [random.randint(3, 10) for _ in range(9)]
|
||||
overall = round(sum(ms) / 9, 1)
|
||||
db.session.add(Evaluation(
|
||||
tryout_id=tryouts[2].id, player_id=player.id,
|
||||
evaluator_id=coach.id,
|
||||
mecanics_score=ms[0], cohesion_score=ms[1],
|
||||
communication_score=ms[2], gamesense_score=ms[3],
|
||||
versatility_score=ms[4], discipline_score=ms[5],
|
||||
analysis_score=ms[6], sport_ethics_score=ms[7],
|
||||
mental_score=ms[8], overall_score=overall,
|
||||
comments=f"{'Excellent' if overall > 8 else 'Solid'} display of skills during the camp.",
|
||||
position_recommendation=random.choice(val_positions)))
|
||||
eval_count += 1
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {eval_count} evaluations")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Tryout-specific teams
|
||||
# -----------------------------------------------------------------------
|
||||
team1 = Team(tryout_id=tryouts[2].id, name='Alpha Team', created_by=admin.id)
|
||||
team2 = Team(tryout_id=tryouts[2].id, name='Bravo Team', created_by=admin.id)
|
||||
db.session.add(team1)
|
||||
db.session.add(team2)
|
||||
db.session.commit()
|
||||
|
||||
val_positions = ['Controller', 'Initiator', 'Duelist', 'Sentinel']
|
||||
team_members_tuples = [
|
||||
(team1, players[0]), (team1, players[1]),
|
||||
(team1, players[2]), (team1, players[3]),
|
||||
(team2, players[4]), (team2, players[5]),
|
||||
]
|
||||
for t, p in team_members_tuples:
|
||||
db.session.add(TeamMember(team_id=t.id, player_id=p.id,
|
||||
position=random.choice(val_positions)))
|
||||
db.session.commit()
|
||||
print("[OK] Created 2 tryout-specific teams with player assignments")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Org-team player assignments
|
||||
# -----------------------------------------------------------------------
|
||||
assignments = [
|
||||
(org_teams[0], players[0], 'starter'),
|
||||
(org_teams[0], players[1], 'starter'),
|
||||
(org_teams[0], players[2], 'substitute'),
|
||||
(org_teams[1], players[3], 'starter'),
|
||||
(org_teams[1], players[4], 'starter'),
|
||||
(org_teams[1], players[5], 'substitute'),
|
||||
(org_teams[2], players[6], 'starter'),
|
||||
(org_teams[2], players[7], 'substitute'),
|
||||
]
|
||||
for team, player, status in assignments:
|
||||
db.session.add(TeamPlayer(player_id=player.id, org_team_id=team.id, status=status))
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(assignments)} org-team player assignments")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Disponibilities
|
||||
# -----------------------------------------------------------------------
|
||||
time_slots = [(17, 0), (17, 30), (18, 0), (18, 30), (19, 0), (19, 30),
|
||||
(20, 0), (20, 30), (21, 0), (21, 30), (22, 0), (22, 30), (23, 0)]
|
||||
disp_count = 0
|
||||
for player in players:
|
||||
for day in range(7):
|
||||
num = random.randint(3, 8)
|
||||
for hour, minute in random.sample(time_slots, min(num, len(time_slots))):
|
||||
end_h, end_m = hour, minute + 30
|
||||
if end_m >= 60:
|
||||
end_m -= 60; end_h += 1
|
||||
db.session.add(PlayerDisponibility(
|
||||
player_id=player.id, day_of_week=day,
|
||||
start_time=time(hour, minute), end_time=time(end_h, end_m)))
|
||||
disp_count += 1
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {disp_count} player disponibilities")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Coach availabilities
|
||||
# -----------------------------------------------------------------------
|
||||
coach_slots = [(16, 0), (16, 30), (17, 0), (17, 30), (18, 0), (18, 30),
|
||||
(19, 0), (19, 30), (20, 0), (20, 30), (21, 0), (21, 30)]
|
||||
ca_count = 0
|
||||
for coach, _ in zip(coaches[:3], org_teams[:3]):
|
||||
days = [0, 1, 2, 3, 4] if coach.id == coaches[2].id else [0, 1, 2, 3, 4, 5]
|
||||
for day in days:
|
||||
num = random.randint(3, 5)
|
||||
for hour, minute in random.sample(coach_slots, min(num, len(coach_slots))):
|
||||
end_h, end_m = hour, minute + 30
|
||||
if end_m >= 60:
|
||||
end_m -= 60; end_h += 1
|
||||
db.session.add(CoachAvailability(
|
||||
coach_id=coach.id, day_of_week=day,
|
||||
start_time=time(hour, minute), end_time=time(end_h, end_m)))
|
||||
ca_count += 1
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {ca_count} coach availabilities")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Team notes
|
||||
# -----------------------------------------------------------------------
|
||||
notes = [
|
||||
(org_teams[0], coaches[0],
|
||||
'Team, focus on rotation and positioning during scrims. '
|
||||
'We need to improve our mechanical consistency and work on post-platoon transitions. '
|
||||
'Remember to communicate clearly and stay positive!'),
|
||||
(org_teams[1], coaches[1],
|
||||
'Great progress this week! Keep working on your smoke lineups and utility usage. '
|
||||
'Individual practice on aim trainers is paying off. Next week we focus on map control and trading.'),
|
||||
(org_teams[2], coaches[2],
|
||||
'Agent comp needs work. Make sure to stick to your roles and trust your teammates. '
|
||||
'Work on your crosshair placement and pre-aim common angles. Team chemistry is key!'),
|
||||
]
|
||||
for team, coach, content in notes:
|
||||
db.session.add(TeamNote(org_team_id=team.id, coach_id=coach.id, content=content))
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(notes)} team notes")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Personal notes
|
||||
# -----------------------------------------------------------------------
|
||||
pnotes = [
|
||||
(players[0], coaches[0], 'Your mechanics are improving! Focus on staying calm during high-pressure situations. Keep practicing those flip resets.'),
|
||||
(players[0], coaches[0], 'Good positioning in last scrim. Work on your kickoffs - consistency will help the team.'),
|
||||
(players[1], coaches[0], 'Your aerial game is strong. Try to be more aggressive on the ball when you have space.'),
|
||||
(players[3], coaches[1], 'Need to work on your smoke grenade placement. Practice pre-aiming and strafe stopping.'),
|
||||
(players[4], coaches[1], 'Good clutch performance! Keep your utility management consistent throughout rounds.'),
|
||||
(players[6], coaches[2], 'Your aim trainer routine is paying off. Work on your agent abilities usage timing.'),
|
||||
(players[7], coaches[2], 'Focus on communication in matches. Call out enemy positions clearly and ask for help when needed.'),
|
||||
]
|
||||
for player, coach, content in pnotes:
|
||||
db.session.add(PersonalNote(player_id=player.id, coach_id=coach.id, content=content))
|
||||
db.session.commit()
|
||||
print(f"[OK] Created {len(pnotes)} personal notes")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Matches
|
||||
# -----------------------------------------------------------------------
|
||||
team1 = Team.query.filter_by(name='Alpha Team').first()
|
||||
team2 = Team.query.filter_by(name='Bravo Team').first()
|
||||
|
||||
m1 = Match(tryout_id=tryouts[0].id, title='Alpha vs Bravo',
|
||||
date=tryouts[0].date, start_time=time(18, 0), end_time=time(18, 30),
|
||||
match_type='team_vs_team', created_by=admin.id,
|
||||
team1_id=team1.id if team1 else None, team2_id=team2.id if team2 else None)
|
||||
m2 = Match(tryout_id=tryouts[0].id, title='Bravo vs Alpha',
|
||||
date=tryouts[0].date, start_time=time(19, 0), end_time=time(19, 30),
|
||||
match_type='team_vs_team', created_by=admin.id,
|
||||
team1_id=team2.id if team2 else None, team2_id=team1.id if team1 else None)
|
||||
m3 = Match(tryout_id=tryouts[1].id, title='Scrimmage',
|
||||
date=tryouts[1].date, start_time=time(17, 0), end_time=time(17, 30),
|
||||
match_type='player_scrim', created_by=admin.id)
|
||||
m4 = Match(tryout_id=tryouts[2].id, title='Team Alpha Scrim',
|
||||
date=tryouts[2].date, start_time=time(18, 30), end_time=time(19, 0),
|
||||
match_type='player_vs_player', created_by=admin.id)
|
||||
db.session.add_all([m1, m2, m3, m4])
|
||||
db.session.commit()
|
||||
print("[OK] Created 4 matches")
|
||||
|
||||
# Match participants
|
||||
for player in players[3:5]:
|
||||
db.session.add(MatchParticipant(match_id=m3.id, player_id=player.id))
|
||||
for player in players[:2]:
|
||||
db.session.add(MatchParticipant(match_id=m4.id, player_id=player.id, team_side=1))
|
||||
for player in players[2:4]:
|
||||
db.session.add(MatchParticipant(match_id=m4.id, player_id=player.id, team_side=2))
|
||||
db.session.commit()
|
||||
print("[OK] Created match participants")
|
||||
|
||||
print("\n[SUCCESS] Database seeded successfully!")
|
||||
print("\n=== Login Credentials ===")
|
||||
print("Admin: username='admin', password='password'")
|
||||
print("Manager: username='manager1', password='password'")
|
||||
print("Coach: username='coach1', password='password'")
|
||||
print("Player: username='jplayer1', password='password'")
|
||||
print("Scout: username='scout1', password='password'")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from app.app import create_app
|
||||
app = create_app()
|
||||
with app.app_context():
|
||||
seed_database()
|
||||
Reference in New Issue
Block a user