Add multi-league foundation

This commit is contained in:
Christian Krakau-Louis
2026-05-22 20:47:28 +02:00
parent e032994628
commit 15e1dc227b
23 changed files with 775 additions and 205 deletions
+153
View File
@@ -104,6 +104,9 @@ def run_migrations(engine):
print("Running migrations...")
# Check if the columns already exist before adding them
# Create league structures and attach existing records to a default league
add_league_support(connection)
# Add additional_oauth_providers column if it doesn't exist
add_oauth_providers_column(connection)
@@ -151,6 +154,156 @@ def add_oauth_providers_column(connection):
except Exception as e:
print(f"Error adding additional_oauth_providers column: {str(e)}")
def add_league_support(connection):
"""Add leagues and backfill existing single-league data."""
try:
inspector = inspect(engine)
tables = inspector.get_table_names()
if 'leagues' not in tables:
print("Creating leagues table")
if engine.name == 'sqlite':
connection.execute(text("""
CREATE TABLE leagues (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) UNIQUE NOT NULL,
slug VARCHAR(120) UNIQUE NOT NULL,
description TEXT,
publisher_name VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
"""))
else:
connection.execute(text("""
CREATE TABLE leagues (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
slug VARCHAR(120) UNIQUE NOT NULL,
description TEXT,
publisher_name VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)
"""))
connection.commit()
league_id = ensure_default_league(connection)
for table_name in ('teams', 'qr_sets', 'qr_codes', 'events'):
add_nullable_league_id(connection, table_name)
backfill_league_ids(connection, league_id)
drop_global_team_name_unique(connection)
except Exception as e:
print(f"Error adding league support: {str(e)}")
def ensure_default_league(connection):
"""Return the default league id, creating it if needed."""
row = connection.execute(
text("SELECT id FROM leagues WHERE slug = :slug"),
{"slug": "default"}
).first()
if row:
return row[0]
connection.execute(
text("""
INSERT INTO leagues (name, slug, description, publisher_name, is_active)
VALUES (:name, :slug, :description, :publisher_name, :is_active)
"""),
{
"name": "Default League",
"slug": "default",
"description": "Default league for existing LeagueLedger data.",
"publisher_name": "LeagueLedger",
"is_active": True,
}
)
connection.commit()
return connection.execute(
text("SELECT id FROM leagues WHERE slug = :slug"),
{"slug": "default"}
).scalar()
def add_nullable_league_id(connection, table_name):
"""Add a nullable league_id column to an existing table."""
inspector = inspect(engine)
if table_name not in inspector.get_table_names():
return
columns = [col['name'] for col in inspector.get_columns(table_name)]
if 'league_id' in columns:
print(f"Column league_id already exists in {table_name}")
return
print(f"Adding league_id column to {table_name}")
if engine.name == 'sqlite':
connection.execute(text(f"ALTER TABLE {table_name} ADD COLUMN league_id INTEGER NULL"))
else:
connection.execute(text(f"ALTER TABLE {table_name} ADD COLUMN league_id INT NULL"))
connection.commit()
def backfill_league_ids(connection, default_league_id):
"""Attach legacy data to the default league."""
inspector = inspect(engine)
tables = inspector.get_table_names()
for table_name in ('teams', 'qr_sets', 'events'):
if table_name in tables and 'league_id' in [col['name'] for col in inspector.get_columns(table_name)]:
connection.execute(
text(f"UPDATE {table_name} SET league_id = :league_id WHERE league_id IS NULL"),
{"league_id": default_league_id}
)
if 'qr_codes' in tables and 'league_id' in [col['name'] for col in inspector.get_columns('qr_codes')]:
connection.execute(text("""
UPDATE qr_codes
SET league_id = (
SELECT qr_sets.league_id
FROM qr_sets
WHERE qr_sets.id = qr_codes.qr_set_id
)
WHERE league_id IS NULL
AND qr_set_id IS NOT NULL
"""))
connection.execute(text("""
UPDATE qr_codes
SET league_id = (
SELECT events.league_id
FROM events
WHERE events.id = qr_codes.event_id
)
WHERE league_id IS NULL
AND event_id IS NOT NULL
"""))
connection.execute(
text("UPDATE qr_codes SET league_id = :league_id WHERE league_id IS NULL"),
{"league_id": default_league_id}
)
connection.commit()
def drop_global_team_name_unique(connection):
"""Best-effort removal of the legacy global team-name uniqueness constraint."""
if engine.name == 'sqlite':
return
inspector = inspect(engine)
if 'teams' not in inspector.get_table_names():
return
for constraint in inspector.get_unique_constraints('teams'):
if constraint.get('column_names') == ['name']:
constraint_name = constraint.get('name')
if constraint_name:
print(f"Dropping global teams.name unique constraint {constraint_name}")
connection.execute(text(f"ALTER TABLE teams DROP INDEX {constraint_name}"))
connection.commit()
break
def add_name_columns(connection):
"""Add first_name and last_name columns to users table"""
try: