gcp-hockey-results/motm_app/main.py

1423 lines
66 KiB
Python

# encoding=utf-8
import pymysql
import os
import json
import hashlib, uuid
import datetime
from datetime import datetime
# Load database configuration first
from db_setup import db_config_manager
db_config_manager.load_config()
db_config_manager._update_environment_variables()
# Reload database modules to pick up new environment variables
import importlib
import database
import db_config
importlib.reload(database)
importlib.reload(db_config)
from app import app, randomUrlSuffix
from flask import Flask, flash, render_template, request, redirect, url_for, jsonify
from sqlalchemy import text
from flask_wtf import FlaskForm
from flask_bootstrap import Bootstrap
from flask_basicauth import BasicAuth
from wtforms import StringField, PasswordField, BooleanField
from wtforms import DateField
from wtforms.validators import InputRequired, Email, Length
from forms import motmForm, adminSettingsForm2, goalsAssistsForm, DatabaseSetupForm, PlayerForm, ClubForm, TeamForm, DataImportForm, ClubSelectionForm
from db_config import sql_write, sql_write_static, sql_read, sql_read_static
from sqlalchemy import text
from tables import matchSquadTable
from readSettings import mySettings
from fixture_scraper import FixtureScraper, get_next_hkfc_c_fixture, get_opponent_club_name, get_opponent_club_info, match_opponent_to_club
from club_scraper import ClubScraper, get_hk_hockey_clubs, expand_club_abbreviation
app.config['BASIC_AUTH_USERNAME'] = 'admin'
app.config['BASIC_AUTH_PASSWORD'] = 'letmein'
basic_auth = BasicAuth(app)
@app.route('/')
def index():
"""Main index page for MOTM system"""
return render_template('index.html')
@app.route('/admin')
@basic_auth.required
def admin_dashboard():
"""Admin dashboard - central hub for all admin functions"""
return render_template('admin_dashboard.html')
# ==================== PUBLIC VOTING SECTION ====================
@app.route('/motm/<randomUrlSuffix>')
def motm_vote(randomUrlSuffix):
"""Public voting page for Man of the Match and Dick of the Day"""
sql = text("SELECT playernumber, playerforenames, playersurname, playernickname FROM _hkfcc_matchsquad ORDER BY RANDOM()")
sql2 = text("SELECT nextclub, nextteam, nextdate, oppologo, hkfclogo, currmotm, currdotd, nextfixture, motmurlsuffix FROM motmadminsettings")
rows = sql_read(sql)
nextInfo = sql_read_static(sql2)
# Handle empty results
if not nextInfo:
return render_template('error.html', message="Database not initialized. Please go to Database Setup to initialize the database.")
nextClub = nextInfo[0]['nextclub']
nextTeam = nextInfo[0]['nextteam']
nextFixture = nextInfo[0]['nextfixture']
hkfcLogo = nextInfo[0]['hkfclogo']
oppoLogo = nextInfo[0]['oppologo']
currMotM = nextInfo[0]['currmotm']
currDotD = nextInfo[0]['currdotd']
oppo = nextTeam
# Get opponent club logo from clubs table
if nextClub:
sql_club_logo = text("SELECT logo_url FROM clubs WHERE hockey_club = :club_name")
club_logo_result = sql_read(sql_club_logo, {'club_name': nextClub})
if club_logo_result and club_logo_result[0]['logo_url']:
oppoLogo = club_logo_result[0]['logo_url']
# Get match date from admin settings
if nextInfo and nextInfo[0]['nextdate']:
nextDate = nextInfo[0]['nextdate']
if isinstance(nextDate, str):
nextDate = datetime.strptime(nextDate, '%Y-%m-%d')
formatDate = nextDate.strftime('%A, %d %B %Y')
else:
return render_template('error.html', message="No match date found. Please set up the next match in admin settings.")
# Get current MOTM and DotD player pictures
if nextInfo and nextInfo[0]['currmotm'] and nextInfo[0]['currdotd']:
sql3 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :curr_motm")
sql4 = text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :curr_dotd")
motm = sql_read(sql3, {'curr_motm': nextInfo[0]['currmotm']})
dotd = sql_read(sql4, {'curr_dotd': nextInfo[0]['currdotd']})
else:
motm = []
dotd = []
# Handle empty results
if not motm or not dotd:
return render_template('error.html', message="Player data not found. Please set up current MOTM and DotD players in admin settings.")
# Use default player images since playerPictureURL column doesn't exist
motmURL = '/static/images/default_player.png'
dotdURL = '/static/images/default_player.png'
# Get match comments
sql5 = text("SELECT comment FROM _motmcomments WHERE matchDate = :match_date ORDER BY RANDOM() LIMIT 1")
comment_result = sql_read(sql5, {'match_date': nextInfo[0]['nextdate']})
comment = comment_result[0]['comment'] if comment_result else "No comments added yet"
form = motmForm()
# Verify URL suffix
if nextInfo and nextInfo[0].get('motmurlsuffix'):
randomSuff = nextInfo[0]['motmurlsuffix']
if randomSuff == randomUrlSuffix:
return render_template('motm_vote.html', data=rows, comment=comment, formatDate=formatDate, matchNumber=nextInfo[0].get('nextfixture', ''), oppo=oppo, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo, dotdURL=dotdURL, motmURL=motmURL, form=form)
else:
return render_template('error.html', message="Invalid voting URL. Please use the correct URL provided by the admin.")
else:
return render_template('error.html', message="Voting not activated. Please contact the admin to activate voting.")
@app.route('/motm/comments', methods=['GET', 'POST'])
def match_comments():
"""Display and allow adding match comments"""
sql = text("SELECT nextClub, nextTeam, nextdate, oppoLogo, hkfcLogo FROM motmadminsettings")
row = sql_read_static(sql)
if not row:
return render_template('error.html', message="Database not initialized. Please go to Database Setup to initialize the database.")
_oppo = row[0]['nextClub']
commentDate = row[0]['nextdate'].strftime('%Y-%m-%d')
_matchDate = row[0]['nextdate'].strftime('%Y_%m_%d')
hkfcLogo = row[0]['hkfcLogo']
oppoLogo = row[0]['oppoLogo']
if request.method == 'POST':
_comment = request.form['matchComment']
if _comment != 'Optional comments added here':
_fixed_comment = _comment.replace("'", "\\'")
sql3 = text("INSERT INTO _motmcomments (matchDate, opposition, comment) VALUES (:comment_date, :opposition, :comment)")
sql_write(sql3, {'comment_date': commentDate, 'opposition': _oppo, 'comment': _fixed_comment})
sql = text("SELECT comment FROM _motmcomments WHERE matchDate = :match_date ORDER BY RANDOM()")
comments = sql_read(sql, {'match_date': _matchDate})
return render_template('match_comments.html', comments=comments, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo)
@app.route('/motm/vote-thanks', methods=['POST'])
def vote_thanks():
"""Process MOTM/DotD votes and comments"""
try:
_motm = request.form['motmVote']
_dotd = request.form['dotdVote']
_comments = request.form['motmComment']
_fixed_comments = _comments.replace("'", "\\'")
_matchDate = request.form['matchNumber']
_oppo = request.form['oppo']
if _motm and _dotd and request.method == 'POST':
# Check if fixture columns exist, create them if not
fixture_date = _matchDate.replace('-', '')
motm_col = f'motm_{fixture_date}'
dotd_col = f'dotd_{fixture_date}'
# Create columns if they don't exist
try:
sql_create_motm = text(f"ALTER TABLE _hkfc_c_motm ADD COLUMN {motm_col} INTEGER DEFAULT 0")
sql_write(sql_create_motm)
except:
pass # Column already exists
try:
sql_create_dotd = text(f"ALTER TABLE _hkfc_c_motm ADD COLUMN {dotd_col} INTEGER DEFAULT 0")
sql_write(sql_create_dotd)
except:
pass # Column already exists
# Get player names
motm_player = sql_read(text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :player_num"), {'player_num': _motm})
dotd_player = sql_read(text("SELECT playernickname FROM _hkfc_players WHERE playernumber = :player_num"), {'player_num': _dotd})
motm_name = motm_player[0]['playernickname'] if motm_player else f'Player {_motm}'
dotd_name = dotd_player[0]['playernickname'] if dotd_player else f'Player {_dotd}'
# Update MOTM vote - use PostgreSQL UPSERT syntax
sql_motm = text(f"""
INSERT INTO _hkfc_c_motm (playernumber, playername, motmtotal, {motm_col})
VALUES (:player_num, :player_name, 1, 1)
ON CONFLICT (playernumber) DO UPDATE SET
motmTotal = _hkfc_c_motm.motmTotal + 1,
{motm_col} = _hkfc_c_motm.{motm_col} + 1
""")
sql_write(sql_motm, {'player_num': _motm, 'player_name': motm_name})
# Update DotD vote - use PostgreSQL UPSERT syntax
sql_dotd = text(f"""
INSERT INTO _hkfc_c_motm (playernumber, playername, dotdtotal, {dotd_col})
VALUES (:player_num, :player_name, 1, 1)
ON CONFLICT (playernumber) DO UPDATE SET
dotdTotal = _hkfc_c_motm.dotdTotal + 1,
{dotd_col} = _hkfc_c_motm.{dotd_col} + 1
""")
sql_write(sql_dotd, {'player_num': _dotd, 'player_name': dotd_name})
# Handle comments
if _comments and _comments != "Optional comments added here":
sql3 = text("INSERT INTO _motmcomments (matchDate, opposition, comment) VALUES (:match_date, :opposition, :comment)")
sql_write(sql3, {'match_date': _matchDate, 'opposition': _oppo, 'comment': _fixed_comments})
return render_template('vote_thanks.html')
else:
return 'Ouch ... something went wrong here'
except Exception as e:
print(e)
finally:
print('Votes cast')
# ==================== ADMIN SECTION ====================
@app.route('/admin/motm', methods=['GET', 'POST'])
@basic_auth.required
def motm_admin():
"""Admin page for managing MOTM settings"""
form = adminSettingsForm2()
prevFixture = mySettings('prevFixture')
if prevFixture is None:
prevFixture = '1'
else:
prevFixture = str(prevFixture)
if request.method == 'POST':
if form.saveButton.data:
print('Saved')
else:
print('Activated')
_nextTeam = request.form.get('nextOppoTeam', '')
_nextMatchDate = request.form.get('nextMatchDate', '')
_currMotM = request.form.get('currMotM', '0')
_currDotD = request.form.get('currDotD', '0')
# Validate required fields
if not _nextTeam or not _nextMatchDate:
flash('Error: Match date and opposition team are required', 'error')
return redirect(url_for('motm_admin'))
# Use the opponent matching system to find the correct club
opponent_club_info = get_opponent_club_info(_nextTeam)
if opponent_club_info and opponent_club_info.get('club_name'):
_nextClub = opponent_club_info['club_name']
else:
# Fallback to old method for backward compatibility
sql1 = "SELECT club FROM _clubTeams WHERE displayName='" + _nextTeam + "'"
_nextClubName = sql_read_static(sql1)
if not _nextClubName:
flash('Error: Club not found for team ' + _nextTeam + '. Please ensure the club exists in the club database.', 'error')
return redirect(url_for('motm_admin'))
_nextClub = _nextClubName[0]['club']
# Only update currMotM and currDotD if they were provided
if _currMotM and _currMotM != '0' and _currDotD and _currDotD != '0':
sql = text("UPDATE motmadminsettings SET nextdate = :next_date, nextClub = :next_club, nextTeam = :next_team, currMotM = :curr_motm, currDotD = :curr_dotd")
sql_write_static(sql, {
'next_date': _nextMatchDate,
'next_club': _nextClub,
'next_team': _nextTeam,
'curr_motm': _currMotM,
'curr_dotd': _currDotD
})
else:
# Don't update currMotM and currDotD if not provided
sql = text("UPDATE motmadminsettings SET nextdate = :next_date, nextClub = :next_club, nextTeam = :next_team")
sql_write_static(sql, {
'next_date': _nextMatchDate,
'next_club': _nextClub,
'next_team': _nextTeam
})
# Update the opponent logo using the matched club information
if opponent_club_info and opponent_club_info.get('logo_url'):
# Use the logo URL from the matched club
logo_url = opponent_club_info['logo_url']
sql2 = text("UPDATE motmadminsettings SET oppologo = :logo_url")
sql_write_static(sql2, {'logo_url': logo_url})
else:
# Fallback to old method
sql2 = text("UPDATE motmadminsettings SET oppologo = (SELECT logo FROM menshockeyclubs WHERE hockeyclub = :next_club) WHERE nextclub = :next_club")
sql_write_static(sql2, {'next_club': _nextClub})
if form.saveButton.data:
flash('Settings saved!')
urlSuffix = randomUrlSuffix(8)
print(urlSuffix)
sql3 = text("UPDATE motmadminsettings SET motmurlsuffix = :url_suffix WHERE userid = 'admin'")
sql_write_static(sql3, {'url_suffix': urlSuffix})
flash('MotM URL https://motm.ervine.cloud/motm/'+urlSuffix)
elif form.activateButton.data:
# Generate a fixture number based on the date
_nextFixture = _nextMatchDate.replace('-', '')
try:
sql4 = text(f"ALTER TABLE _hkfc_c_motm ADD COLUMN motm_{_nextFixture} smallint DEFAULT 0, ADD COLUMN dotd_{_nextFixture} smallint DEFAULT 0, ADD COLUMN assists_{_nextFixture} smallint DEFAULT 0, ADD COLUMN goals_{_nextFixture} smallint DEFAULT 0")
sql_write(sql4)
except Exception as e:
# Columns already exist, which is fine
print(f"Columns already exist for fixture {_nextFixture}: {e}")
pass
sql5 = text("SELECT motmurlsuffix FROM motmadminsettings WHERE userid = 'admin'")
tempSuffix = sql_read_static(sql5)
if not tempSuffix:
flash('Error: Admin settings not found', 'error')
return redirect(url_for('motm_admin'))
currSuffix = tempSuffix[0]['motmurlsuffix']
print(currSuffix)
flash('Man of the Match vote is now activated')
flash('MotM URL https://motm.ervine.cloud/motm/'+currSuffix)
else:
flash('Something went wrong - check with Smithers')
# Load current settings to populate the form
sql_current = text("SELECT nextdate FROM motmadminsettings WHERE userid = 'admin'")
current_settings = sql_read_static(sql_current)
if current_settings:
from datetime import datetime
try:
current_date = current_settings[0]['nextdate']
if isinstance(current_date, str):
current_date = datetime.strptime(current_date, '%Y-%m-%d').date()
form.nextMatchDate.data = current_date
except:
pass
sql4 = text("SELECT hockeyclub FROM menshockeyclubs ORDER BY hockeyclub")
sql5 = text("SELECT nextclub, oppologo FROM motmadminsettings")
sql6 = text(f"SELECT playernumber, playerforenames, playersurname FROM _hkfcc_matchsquad_{prevFixture} ORDER BY playerforenames")
clubs = sql_read_static(sql4)
settings = sql_read_static(sql5)
players = sql_read(sql6)
# Handle empty results gracefully
if not clubs:
clubs = []
if not settings:
settings = [{'nextclub': 'Unknown', 'oppologo': '/static/images/default_logo.png'}]
if not players:
players = []
form.nextOppoClub.choices = [(oppo['hockeyclub'], oppo['hockeyclub']) for oppo in clubs]
form.currMotM.choices = [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
form.currDotD.choices = [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
clubLogo = settings[0]['oppologo']
return render_template('motm_admin.html', form=form, nextOppoLogo=clubLogo)
@app.route('/admin/players', methods=['GET'])
@basic_auth.required
def player_management():
"""Admin page for managing players"""
sql = text("SELECT playernumber, playerforenames, playersurname, playernickname, playerteam FROM _hkfc_players ORDER BY playernumber")
players = sql_read(sql)
return render_template('player_management.html', players=players)
@app.route('/admin/players/add', methods=['GET', 'POST'])
@basic_auth.required
def add_player():
"""Add a new player"""
form = PlayerForm()
if form.validate_on_submit():
if form.save_player.data:
# Check if player number already exists
sql_check = text("SELECT playernumber FROM _hkfc_players WHERE playernumber = :player_number")
existing = sql_read(sql_check, {'player_number': form.player_number.data})
if existing:
flash('Player number already exists!', 'error')
return render_template('add_player.html', form=form)
# Insert new player
sql = text("INSERT INTO _hkfc_players (playernumber, playerforenames, playersurname, playernickname, playerteam) VALUES (:player_number, :forenames, :surname, :nickname, :team)")
sql_write(sql, {
'player_number': form.player_number.data,
'forenames': form.player_forenames.data,
'surname': form.player_surname.data,
'nickname': form.player_nickname.data,
'team': form.player_team.data
})
flash('Player added successfully!', 'success')
return redirect(url_for('player_management'))
elif form.cancel.data:
return redirect(url_for('player_management'))
return render_template('add_player.html', form=form)
@app.route('/admin/players/edit/<int:player_number>', methods=['GET', 'POST'])
@basic_auth.required
def edit_player(player_number):
"""Edit an existing player"""
form = PlayerForm()
if request.method == 'GET':
# Load player data
sql = text("SELECT playernumber, playerforenames, playersurname, playernickname, playerteam FROM _hkfc_players WHERE playernumber = :player_number")
player_data = sql_read(sql, {'player_number': player_number})
if not player_data:
flash('Player not found!', 'error')
return redirect(url_for('player_management'))
player = player_data[0]
form.player_number.data = player['playernumber']
form.player_forenames.data = player['playerforenames']
form.player_surname.data = player['playersurname']
form.player_nickname.data = player['playernickname']
form.player_team.data = player['playerteam']
if form.validate_on_submit():
if form.save_player.data:
# Update player
sql = text("UPDATE _hkfc_players SET playerforenames = :forenames, playersurname = :surname, playernickname = :nickname, playerteam = :team WHERE playernumber = :player_number")
sql_write(sql, {
'forenames': form.player_forenames.data,
'surname': form.player_surname.data,
'nickname': form.player_nickname.data,
'team': form.player_team.data,
'player_number': player_number
})
flash('Player updated successfully!', 'success')
return redirect(url_for('player_management'))
elif form.cancel.data:
return redirect(url_for('player_management'))
return render_template('edit_player.html', form=form, player_number=player_number)
@app.route('/admin/players/delete/<int:player_number>', methods=['POST'])
@basic_auth.required
def delete_player(player_number):
"""Delete a player"""
sql = text("DELETE FROM _hkfc_players WHERE playernumber = :player_number")
sql_write(sql, {'player_number': player_number})
flash('Player deleted successfully!', 'success')
return redirect(url_for('player_management'))
@app.route('/admin/squad', methods=['GET'])
@basic_auth.required
def match_squad():
"""Admin page for managing match squad"""
sql = text("SELECT playernumber, playerforenames, playersurname, playernickname, playerteam FROM _hkfc_players ORDER BY playerteam, playernumber")
players = sql_read(sql)
return render_template('match_squad.html', players=players)
# ==================== CLUB MANAGEMENT ====================
@app.route('/admin/clubs', methods=['GET'])
@basic_auth.required
def club_management():
"""Admin page for managing clubs"""
sql = text("SELECT id, hockey_club, logo_url FROM clubs ORDER BY hockey_club")
clubs = sql_read(sql)
return render_template('club_management.html', clubs=clubs)
@app.route('/admin/clubs/add', methods=['GET', 'POST'])
@basic_auth.required
def add_club():
"""Add a new club"""
form = ClubForm()
if form.validate_on_submit():
if form.save_club.data:
# Check if club already exists
sql_check = text("SELECT hockey_club FROM clubs WHERE hockey_club = :club_name")
existing = sql_read(sql_check, {'club_name': form.hockey_club.data})
if existing:
flash('Club already exists!', 'error')
return render_template('add_club.html', form=form)
# Insert new club
sql = text("INSERT INTO clubs (hockey_club, logo_url) VALUES (:club_name, :logo_url)")
sql_write(sql, {
'club_name': form.hockey_club.data,
'logo_url': form.logo_url.data
})
flash('Club added successfully!', 'success')
return redirect(url_for('club_management'))
elif form.cancel.data:
return redirect(url_for('club_management'))
return render_template('add_club.html', form=form)
@app.route('/admin/clubs/edit/<int:club_id>', methods=['GET', 'POST'])
@basic_auth.required
def edit_club(club_id):
"""Edit an existing club"""
form = ClubForm()
if request.method == 'GET':
# Load club data
sql = text("SELECT id, hockey_club, logo_url FROM clubs WHERE id = :club_id")
club_data = sql_read(sql, {'club_id': club_id})
if not club_data:
flash('Club not found!', 'error')
return redirect(url_for('club_management'))
club = club_data[0]
form.hockey_club.data = club['hockey_club']
form.logo_url.data = club['logo_url']
if form.validate_on_submit():
if form.save_club.data:
# Update club
sql = text("UPDATE clubs SET hockey_club = :club_name, logo_url = :logo_url WHERE id = :club_id")
sql_write(sql, {
'club_name': form.hockey_club.data,
'logo_url': form.logo_url.data,
'club_id': club_id
})
flash('Club updated successfully!', 'success')
return redirect(url_for('club_management'))
elif form.cancel.data:
return redirect(url_for('club_management'))
return render_template('edit_club.html', form=form, club_id=club_id)
@app.route('/admin/clubs/delete/<int:club_id>', methods=['POST'])
@basic_auth.required
def delete_club(club_id):
"""Delete a club"""
sql = text("DELETE FROM clubs WHERE id = :club_id")
sql_write(sql, {'club_id': club_id})
flash('Club deleted successfully!', 'success')
return redirect(url_for('club_management'))
# ==================== TEAM MANAGEMENT ====================
@app.route('/admin/teams', methods=['GET'])
@basic_auth.required
def team_management():
"""Admin page for managing teams"""
sql = text("SELECT id, club, team, display_name, league FROM teams ORDER BY club, team")
teams = sql_read(sql)
return render_template('team_management.html', teams=teams)
@app.route('/admin/teams/add', methods=['GET', 'POST'])
@basic_auth.required
def add_team():
"""Add a new team"""
form = TeamForm()
# Populate club choices
sql_clubs = text("SELECT hockey_club FROM clubs ORDER BY hockey_club")
clubs = sql_read(sql_clubs)
form.club.choices = [(club['hockey_club'], club['hockey_club']) for club in clubs]
if form.validate_on_submit():
if form.save_team.data:
# Check if team already exists
sql_check = text("SELECT club, team FROM teams WHERE club = :club AND team = :team")
existing = sql_read(sql_check, {'club': form.club.data, 'team': form.team.data})
if existing:
flash('Team already exists!', 'error')
return render_template('add_team.html', form=form)
# Insert new team
sql = text("INSERT INTO teams (club, team, display_name, league) VALUES (:club, :team, :display_name, :league)")
sql_write(sql, {
'club': form.club.data,
'team': form.team.data,
'display_name': form.display_name.data,
'league': form.league.data
})
flash('Team added successfully!', 'success')
return redirect(url_for('team_management'))
elif form.cancel.data:
return redirect(url_for('team_management'))
return render_template('add_team.html', form=form)
@app.route('/admin/teams/edit/<int:team_id>', methods=['GET', 'POST'])
@basic_auth.required
def edit_team(team_id):
"""Edit an existing team"""
form = TeamForm()
# Populate club choices
sql_clubs = text("SELECT hockey_club FROM clubs ORDER BY hockey_club")
clubs = sql_read(sql_clubs)
form.club.choices = [(club['hockey_club'], club['hockey_club']) for club in clubs]
if request.method == 'GET':
# Load team data
sql = text("SELECT id, club, team, display_name, league FROM teams WHERE id = :team_id")
team_data = sql_read(sql, {'team_id': team_id})
if not team_data:
flash('Team not found!', 'error')
return redirect(url_for('team_management'))
team = team_data[0]
form.club.data = team['club']
form.team.data = team['team']
form.display_name.data = team['display_name']
form.league.data = team['league']
if form.validate_on_submit():
if form.save_team.data:
# Update team
sql = text("UPDATE teams SET club = :club, team = :team, display_name = :display_name, league = :league WHERE id = :team_id")
sql_write(sql, {
'club': form.club.data,
'team': form.team.data,
'display_name': form.display_name.data,
'league': form.league.data,
'team_id': team_id
})
flash('Team updated successfully!', 'success')
return redirect(url_for('team_management'))
elif form.cancel.data:
return redirect(url_for('team_management'))
return render_template('edit_team.html', form=form, team_id=team_id)
@app.route('/admin/teams/delete/<int:team_id>', methods=['POST'])
@basic_auth.required
def delete_team(team_id):
"""Delete a team"""
sql = text("DELETE FROM teams WHERE id = :team_id")
sql_write(sql, {'team_id': team_id})
flash('Team deleted successfully!', 'success')
return redirect(url_for('team_management'))
# ==================== DATA IMPORT ====================
@app.route('/admin/import', methods=['GET', 'POST'])
@basic_auth.required
def data_import():
"""Import data from Hong Kong Hockey Association"""
form = DataImportForm()
if form.validate_on_submit():
if form.import_data.data:
imported_clubs = 0
imported_teams = 0
imported_players = 0
if form.import_clubs.data:
# Import clubs based on Hong Kong Hockey Association data
clubs_data = [
{'hockey_club': 'HKFC', 'logo_url': '/static/images/hkfc_logo.png'},
{'hockey_club': 'KCC', 'logo_url': '/static/images/kcc_logo.png'},
{'hockey_club': 'USRC', 'logo_url': '/static/images/usrc_logo.png'},
{'hockey_club': 'Valley', 'logo_url': '/static/images/valley_logo.png'},
{'hockey_club': 'SSSC', 'logo_url': '/static/images/sssc_logo.png'},
{'hockey_club': 'Dragons', 'logo_url': '/static/images/dragons_logo.png'},
{'hockey_club': 'Kai Tak', 'logo_url': '/static/images/kaitak_logo.png'},
{'hockey_club': 'RHOBA', 'logo_url': '/static/images/rhoba_logo.png'},
{'hockey_club': 'Elite', 'logo_url': '/static/images/elite_logo.png'},
{'hockey_club': 'Aquila', 'logo_url': '/static/images/aquila_logo.png'},
{'hockey_club': 'HKJ', 'logo_url': '/static/images/hkj_logo.png'},
{'hockey_club': 'Sirius', 'logo_url': '/static/images/sirius_logo.png'},
{'hockey_club': 'Shaheen', 'logo_url': '/static/images/shaheen_logo.png'},
{'hockey_club': 'Diocesan', 'logo_url': '/static/images/diocesan_logo.png'},
{'hockey_club': 'Rhino', 'logo_url': '/static/images/rhino_logo.png'},
{'hockey_club': 'Khalsa', 'logo_url': '/static/images/khalsa_logo.png'},
{'hockey_club': 'HKCC', 'logo_url': '/static/images/hkcc_logo.png'},
{'hockey_club': 'Police', 'logo_url': '/static/images/police_logo.png'},
{'hockey_club': 'Recreio', 'logo_url': '/static/images/recreio_logo.png'},
{'hockey_club': 'CSD', 'logo_url': '/static/images/csd_logo.png'},
{'hockey_club': 'Dutch', 'logo_url': '/static/images/dutch_logo.png'},
{'hockey_club': 'HKUHC', 'logo_url': '/static/images/hkuhc_logo.png'},
{'hockey_club': 'Kaitiaki', 'logo_url': '/static/images/kaitiaki_logo.png'},
{'hockey_club': 'Antlers', 'logo_url': '/static/images/antlers_logo.png'},
{'hockey_club': 'Marcellin', 'logo_url': '/static/images/marcellin_logo.png'},
{'hockey_club': 'Skyers', 'logo_url': '/static/images/skyers_logo.png'},
{'hockey_club': 'JR', 'logo_url': '/static/images/jr_logo.png'},
{'hockey_club': 'IUHK', 'logo_url': '/static/images/iuhk_logo.png'},
{'hockey_club': '144U', 'logo_url': '/static/images/144u_logo.png'},
{'hockey_club': 'HKU', 'logo_url': '/static/images/hku_logo.png'},
]
for club_data in clubs_data:
# Check if club already exists
sql_check = text("SELECT hockey_club FROM clubs WHERE hockey_club = :club_name")
existing = sql_read(sql_check, {'club_name': club_data['hockey_club']})
if not existing:
sql = text("INSERT INTO clubs (hockey_club, logo_url) VALUES (:club_name, :logo_url)")
sql_write(sql, club_data)
imported_clubs += 1
if form.import_teams.data:
# Import teams based on Hong Kong Hockey Association divisions
teams_data = [
# Premier Division
{'club': 'HKFC', 'team': 'A', 'display_name': 'HKFC A', 'league': 'Premier Division'},
{'club': 'KCC', 'team': 'A', 'display_name': 'KCC A', 'league': 'Premier Division'},
{'club': 'USRC', 'team': 'A', 'display_name': 'USRC A', 'league': 'Premier Division'},
{'club': 'Valley', 'team': 'A', 'display_name': 'Valley A', 'league': 'Premier Division'},
# 1st Division
{'club': 'HKFC', 'team': 'B', 'display_name': 'HKFC B', 'league': '1st Division'},
{'club': 'KCC', 'team': 'B', 'display_name': 'KCC B', 'league': '1st Division'},
{'club': 'USRC', 'team': 'B', 'display_name': 'USRC B', 'league': '1st Division'},
{'club': 'Valley', 'team': 'B', 'display_name': 'Valley B', 'league': '1st Division'},
# 2nd Division
{'club': 'HKFC', 'team': 'C', 'display_name': 'HKFC C', 'league': '2nd Division'},
{'club': 'KCC', 'team': 'C', 'display_name': 'KCC C', 'league': '2nd Division'},
{'club': 'USRC', 'team': 'C', 'display_name': 'USRC C', 'league': '2nd Division'},
{'club': 'Valley', 'team': 'C', 'display_name': 'Valley C', 'league': '2nd Division'},
# 3rd Division
{'club': 'SSSC', 'team': 'C', 'display_name': 'SSSC C', 'league': '3rd Division'},
{'club': 'Dragons', 'team': 'A', 'display_name': 'Dragons A', 'league': '3rd Division'},
{'club': 'Kai Tak', 'team': 'B', 'display_name': 'Kai Tak B', 'league': '3rd Division'},
{'club': 'RHOBA', 'team': 'A', 'display_name': 'RHOBA A', 'league': '3rd Division'},
{'club': 'Elite', 'team': 'B', 'display_name': 'Elite B', 'league': '3rd Division'},
{'club': 'HKFC', 'team': 'F', 'display_name': 'HKFC F', 'league': '3rd Division'},
{'club': 'Aquila', 'team': 'A', 'display_name': 'Aquila A', 'league': '3rd Division'},
{'club': 'HKJ', 'team': 'B', 'display_name': 'HKJ B', 'league': '3rd Division'},
{'club': 'Sirius', 'team': 'A', 'display_name': 'Sirius A', 'league': '3rd Division'},
{'club': 'Shaheen', 'team': 'B', 'display_name': 'Shaheen B', 'league': '3rd Division'},
{'club': 'RHOBA', 'team': 'B', 'display_name': 'RHOBA B', 'league': '3rd Division'},
# 4th Division
{'club': 'Khalsa', 'team': 'C', 'display_name': 'Khalsa C', 'league': '4th Division'},
{'club': 'HKCC', 'team': 'C', 'display_name': 'HKCC C', 'league': '4th Division'},
{'club': 'Valley', 'team': 'D', 'display_name': 'Valley D', 'league': '4th Division'},
{'club': 'Police', 'team': 'A', 'display_name': 'Police A', 'league': '4th Division'},
{'club': 'Recreio', 'team': 'A', 'display_name': 'Recreio A', 'league': '4th Division'},
{'club': 'CSD', 'team': 'A', 'display_name': 'CSD A', 'league': '4th Division'},
{'club': 'HKFC', 'team': 'G', 'display_name': 'HKFC G', 'league': '4th Division'},
{'club': 'Dutch', 'team': 'B', 'display_name': 'Dutch B', 'league': '4th Division'},
{'club': 'RHOBA', 'team': 'C', 'display_name': 'RHOBA C', 'league': '4th Division'},
{'club': 'HKUHC', 'team': 'A', 'display_name': 'HKUHC A', 'league': '4th Division'},
{'club': 'Kaitiaki', 'team': 'A', 'display_name': 'Kaitiaki', 'league': '4th Division'},
# 5th Division
{'club': 'KCC', 'team': 'D', 'display_name': 'KCC D', 'league': '5th Division'},
{'club': 'Kai Tak', 'team': 'C', 'display_name': 'Kai Tak C', 'league': '5th Division'},
{'club': 'Dragons', 'team': 'B', 'display_name': 'Dragons B', 'league': '5th Division'},
{'club': 'Antlers', 'team': 'C', 'display_name': 'Antlers C', 'league': '5th Division'},
{'club': 'Valley', 'team': 'E', 'display_name': 'Valley E', 'league': '5th Division'},
{'club': 'Elite', 'team': 'C', 'display_name': 'Elite C', 'league': '5th Division'},
{'club': 'HKFC', 'team': 'H', 'display_name': 'HKFC H', 'league': '5th Division'},
{'club': 'Aquila', 'team': 'B', 'display_name': 'Aquila B', 'league': '5th Division'},
{'club': 'Sirius', 'team': 'B', 'display_name': 'Sirius B', 'league': '5th Division'},
{'club': 'Marcellin', 'team': 'A', 'display_name': 'Marcellin A', 'league': '5th Division'},
{'club': 'Recreio', 'team': 'B', 'display_name': 'Recreio B', 'league': '5th Division'},
{'club': 'Diocesan', 'team': 'B', 'display_name': 'Diocesan B', 'league': '5th Division'},
# 6th Division
{'club': 'Rhino', 'team': 'B', 'display_name': 'Rhino B', 'league': '6th Division'},
{'club': 'Skyers', 'team': 'A', 'display_name': 'Skyers A', 'league': '6th Division'},
{'club': 'JR', 'team': 'A', 'display_name': 'JR', 'league': '6th Division'},
{'club': 'HKCC', 'team': 'D', 'display_name': 'HKCC D', 'league': '6th Division'},
{'club': 'KCC', 'team': 'E', 'display_name': 'KCC E', 'league': '6th Division'},
{'club': 'HKJ', 'team': 'C', 'display_name': 'HKJ C', 'league': '6th Division'},
{'club': 'IUHK', 'team': 'A', 'display_name': 'IUHK A', 'league': '6th Division'},
{'club': 'Valley', 'team': 'F', 'display_name': 'Valley F', 'league': '6th Division'},
{'club': '144U', 'team': 'A', 'display_name': '144U A', 'league': '6th Division'},
{'club': 'HKU', 'team': 'A', 'display_name': 'HKU A', 'league': '6th Division'},
]
for team_data in teams_data:
# Check if team already exists
sql_check = text("SELECT club, team FROM teams WHERE club = :club AND team = :team")
existing = sql_read(sql_check, {'club': team_data['club'], 'team': team_data['team']})
if not existing:
sql = text("INSERT INTO teams (club, team, display_name, league) VALUES (:club, :team, :display_name, :league)")
sql_write(sql, team_data)
imported_teams += 1
if form.import_players.data:
# Import sample players for HKFC C team
players_data = [
{'player_number': 1, 'player_forenames': 'John', 'player_surname': 'Smith', 'player_nickname': 'Smithers', 'player_team': 'HKFC C'},
{'player_number': 2, 'player_forenames': 'Mike', 'player_surname': 'Jones', 'player_nickname': 'Jonesy', 'player_team': 'HKFC C'},
{'player_number': 3, 'player_forenames': 'David', 'player_surname': 'Brown', 'player_nickname': 'Brownie', 'player_team': 'HKFC C'},
{'player_number': 4, 'player_forenames': 'Chris', 'player_surname': 'Wilson', 'player_nickname': 'Willy', 'player_team': 'HKFC C'},
{'player_number': 5, 'player_forenames': 'Tom', 'player_surname': 'Taylor', 'player_nickname': 'Tayls', 'player_team': 'HKFC C'},
]
for player_data in players_data:
# Check if player already exists
sql_check = text("SELECT playernumber FROM _hkfc_players WHERE playernumber = :player_number")
existing = sql_read(sql_check, {'player_number': player_data['player_number']})
if not existing:
sql = text("INSERT INTO _hkfc_players (playernumber, playerforenames, playersurname, playernickname, playerteam) VALUES (:player_number, :forenames, :surname, :nickname, :team)")
sql_write(sql, player_data)
imported_players += 1
flash(f'Import completed! {imported_clubs} clubs, {imported_teams} teams, {imported_players} players imported.', 'success')
return redirect(url_for('data_import'))
elif form.cancel.data:
return redirect(url_for('data_import'))
return render_template('data_import.html', form=form)
@app.route('/admin/import/clubs/select', methods=['GET', 'POST'])
@basic_auth.required
def club_selection():
"""Club selection page for importing specific clubs"""
form = ClubSelectionForm()
# Fetch clubs from the website
try:
clubs = get_hk_hockey_clubs()
if not clubs:
# Fallback to predefined clubs if scraping fails
clubs = [
{'name': 'Hong Kong Football Club', 'abbreviation': 'HKFC', 'teams': ['A', 'B', 'C', 'F', 'G', 'H'], 'convenor': None, 'email': None},
{'name': 'Kowloon Cricket Club', 'abbreviation': 'KCC', 'teams': ['A', 'B', 'C', 'D', 'E'], 'convenor': None, 'email': None},
{'name': 'United Services Recreation Club', 'abbreviation': 'USRC', 'teams': ['A', 'B', 'C'], 'convenor': None, 'email': None},
{'name': 'Valley Fort Sports Club', 'abbreviation': 'Valley', 'teams': ['A', 'B', 'C', 'D', 'E', 'F'], 'convenor': None, 'email': None},
{'name': 'South China Sports Club', 'abbreviation': 'SSSC', 'teams': ['C'], 'convenor': None, 'email': None},
{'name': 'Dragons Hockey Club', 'abbreviation': 'Dragons', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
{'name': 'Kai Tak Sports Club', 'abbreviation': 'Kai Tak', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
{'name': 'Royal Hong Kong Regiment Officers and Businessmen Association', 'abbreviation': 'RHOBA', 'teams': ['A', 'B', 'C'], 'convenor': None, 'email': None},
{'name': 'Elite Hockey Club', 'abbreviation': 'Elite', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
{'name': 'Aquila Hockey Club', 'abbreviation': 'Aquila', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
{'name': 'Hong Kong Jockey Club', 'abbreviation': 'HKJ', 'teams': ['B', 'C'], 'convenor': None, 'email': None},
{'name': 'Sirius Hockey Club', 'abbreviation': 'Sirius', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
{'name': 'Shaheen Hockey Club', 'abbreviation': 'Shaheen', 'teams': ['B'], 'convenor': None, 'email': None},
{'name': 'Diocesan Boys School', 'abbreviation': 'Diocesan', 'teams': ['B'], 'convenor': None, 'email': None},
{'name': 'Rhino Hockey Club', 'abbreviation': 'Rhino', 'teams': ['B'], 'convenor': None, 'email': None},
{'name': 'Khalsa Hockey Club', 'abbreviation': 'Khalsa', 'teams': ['C'], 'convenor': None, 'email': None},
{'name': 'Hong Kong Cricket Club', 'abbreviation': 'HKCC', 'teams': ['C', 'D'], 'convenor': None, 'email': None},
{'name': 'Hong Kong Police Force', 'abbreviation': 'Police', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Recreio Hockey Club', 'abbreviation': 'Recreio', 'teams': ['A', 'B'], 'convenor': None, 'email': None},
{'name': 'Correctional Services Department', 'abbreviation': 'CSD', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Dutch Hockey Club', 'abbreviation': 'Dutch', 'teams': ['B'], 'convenor': None, 'email': None},
{'name': 'Hong Kong University Hockey Club', 'abbreviation': 'HKUHC', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Kaitiaki Hockey Club', 'abbreviation': 'Kaitiaki', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Antlers Hockey Club', 'abbreviation': 'Antlers', 'teams': ['C'], 'convenor': None, 'email': None},
{'name': 'Marcellin Hockey Club', 'abbreviation': 'Marcellin', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Skyers Hockey Club', 'abbreviation': 'Skyers', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'JR Hockey Club', 'abbreviation': 'JR', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'International University of Hong Kong', 'abbreviation': 'IUHK', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': '144 United Hockey Club', 'abbreviation': '144U', 'teams': ['A'], 'convenor': None, 'email': None},
{'name': 'Hong Kong University', 'abbreviation': 'HKU', 'teams': ['A'], 'convenor': None, 'email': None},
]
except Exception as e:
flash(f'Error fetching clubs: {str(e)}', 'error')
clubs = []
if request.method == 'POST':
if form.select_all.data:
# Select all clubs
selected_clubs = [club['name'] for club in clubs]
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=selected_clubs)
elif form.select_none.data:
# Select no clubs
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
elif form.import_selected.data:
# Import selected clubs
selected_clubs = request.form.getlist('selected_clubs')
if not selected_clubs:
flash('No clubs selected for import!', 'error')
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
imported_count = 0
skipped_count = 0
for club_name in selected_clubs:
# Find the club data
club_data = next((club for club in clubs if club['name'] == club_name), None)
if not club_data:
continue
# Check if club already exists
sql_check = text("SELECT hockey_club FROM clubs WHERE hockey_club = :club_name")
existing = sql_read(sql_check, {'club_name': club_name})
if existing:
skipped_count += 1
continue
# Import the club
logo_url = f"/static/images/clubs/{club_name.lower().replace(' ', '_').replace('.', '').replace(',', '')}_logo.png"
sql = text("INSERT INTO clubs (hockey_club, logo_url) VALUES (:club_name, :logo_url)")
sql_write(sql, {'club_name': club_name, 'logo_url': logo_url})
imported_count += 1
if imported_count > 0:
flash(f'Successfully imported {imported_count} clubs!', 'success')
if skipped_count > 0:
flash(f'Skipped {skipped_count} clubs that already exist.', 'info')
return redirect(url_for('club_management'))
elif form.cancel.data:
return redirect(url_for('data_import'))
return render_template('club_selection.html', form=form, clubs=clubs, selected_clubs=[])
@app.route('/admin/squad/submit', methods=['POST'])
@basic_auth.required
def match_squad_submit():
"""Process squad selection"""
_playerNumbers = request.form.getlist('playerNumber')
if not _playerNumbers:
flash('No players selected!', 'error')
return redirect(url_for('match_squad'))
for _playerNumber in _playerNumbers:
sql = text("INSERT INTO _hkfcc_matchsquad (playernumber, playerforenames, playersurname, playernickname) SELECT playernumber, playerforenames, playersurname, playernickname FROM _hkfc_players WHERE playernumber = :player_number")
sql_write(sql, {'player_number': _playerNumber})
flash(f'Successfully added {len(_playerNumbers)} player(s) to the squad!', 'success')
sql2 = text("SELECT playernumber, playerforenames, playersurname, playernickname FROM _hkfcc_matchsquad")
players = sql_read(sql2)
table = matchSquadTable(players)
table.border = True
table.classes = ['table-striped', 'table-condensed', 'table-hover']
return render_template('match_squad_selected.html', table=table)
@app.route('/admin/squad/list')
@basic_auth.required
def match_squad_list():
"""Display current squad list"""
sql = text("SELECT playernumber, playerforenames, playersurname, playernickname FROM _hkfcc_matchsquad")
players = sql_read(sql)
table = matchSquadTable(players)
table.border = True
table.classes = ['table-striped', 'table-condensed', 'table-hover']
return render_template('match_squad_selected.html', table=table)
@app.route('/admin/squad/remove', methods=['POST'])
@basic_auth.required
def delPlayerFromSquad():
"""Remove player from squad"""
_playerNumber = request.args.get('playerNumber')
if not _playerNumber:
flash('Player number not provided', 'error')
return redirect(url_for('match_squad_list'))
sql = text("DELETE FROM _hkfcc_matchsquad WHERE playernumber = :player_number")
sql_write(sql, {'player_number': _playerNumber})
flash(f'Player #{_playerNumber} removed from squad', 'success')
return redirect(url_for('match_squad_list'))
@app.route('/admin/squad/reset')
@basic_auth.required
def matchSquadReset():
"""Reset squad for new match"""
_matchNumber = str(mySettings('fixture'))
print(_matchNumber)
try:
# First, check if there are any players in the current squad
check_sql = text("SELECT COUNT(*) as count FROM _hkfcC_matchSquad")
result = sql_read(check_sql)
squad_count = result[0]['count'] if result else 0
if squad_count > 0:
# Rename current squad table
sql1 = text(f"RENAME TABLE _hkfcC_matchSquad TO _hkfcC_matchSquad_{_matchNumber}")
sql_write(sql1)
# Create new empty squad table
sql2 = text("CREATE TABLE _hkfcC_matchSquad (playerNumber smallint UNIQUE, playerForenames varchar(50), playerSurname varchar(30), playerNickname varchar(30) NOT NULL, PRIMARY KEY (playerNumber))")
sql_write(sql2)
# Update fixture number
sql3 = text("UPDATE motmAdminSettings SET prevFixture = :match_number")
sql_write_static(sql3, {'match_number': _matchNumber})
flash(f'Squad reset successfully! {squad_count} players archived for match {_matchNumber}', 'success')
else:
flash('No players in current squad to reset', 'info')
except Exception as e:
flash(f'Error resetting squad: {str(e)}', 'error')
return render_template('match_squad_reset.html')
@app.route('/admin/stats', methods=['GET', 'POST'])
@basic_auth.required
def stats_admin():
"""Admin page for managing goals and assists statistics"""
form = goalsAssistsForm()
sql = "SELECT date, homeTeam, awayTeam, venue, fixtureNumber FROM hockeyFixtures WHERE homeTeam='HKFC C' OR awayTeam='HKFC C'"
matches = sql_read(sql)
form.match.choices = [(match['fixtureNumber'], match['date']) for match in matches]
sql2 = "SELECT playerNumber, playerNickname FROM _hkfcC_matchSquad"
players = sql_read(sql2)
return render_template('goals_assists_admin.html', data=players, form=form)
@app.route('/admin/stats/submit', methods=['POST'])
@basic_auth.required
def goalsAssistsSubmit():
"""Process goals and assists statistics"""
try:
data = request.form
playerName = request.form.getlist('playerName')
playerNumber = request.form.getlist('playerNumber')
assists = request.form.getlist('assists')
goals = request.form.getlist('goals')
match = request.form['match']
for idx, player in enumerate(playerNumber):
sql = "INSERT INTO _hkfc_c_motm (playerNumber, playerName, assistsTotal, goalsTotal, assists_" + match + ", goals_" + match + ") SELECT playerNumber, playerNickname, '" + assists[idx] + "', '" + goals[idx] + "', '" + assists[idx] + "', '" + goals[idx] + "' FROM _HKFC_players WHERE playerNumber='" + player + "' ON DUPLICATE KEY UPDATE assistsTotal = assistsTotal + " + assists[idx] + ", goalsTotal = goalsTotal + " + goals[idx] + ", assists_" + match + " = " + assists[idx] + ", goals_" + match + " = " + goals[idx] + ""
sql_write(sql)
except Exception as e:
print(e)
finally:
return render_template('goals_thanks.html', data=data)
# ==================== API ENDPOINTS ====================
@app.route('/admin/api/next-fixture')
@basic_auth.required
def get_next_fixture():
"""API endpoint to fetch the next HKFC C fixture from Hockey Hong Kong website"""
try:
fixture = get_next_hkfc_c_fixture()
if fixture:
# Get opponent club information
opponent_club_info = get_opponent_club_info(fixture['opponent'])
# Format the fixture data for JSON response
fixture_data = {
'success': True,
'date': fixture['date'].strftime('%Y-%m-%d'),
'date_formatted': fixture['date'].strftime('%A, %d %B %Y'),
'time': fixture['time'],
'venue': fixture['venue'],
'opponent': fixture['opponent'],
'opponent_club': get_opponent_club_name(fixture['opponent']),
'opponent_club_info': opponent_club_info,
'is_home': fixture['is_home'],
'home_team': fixture['home_team'],
'away_team': fixture['away_team'],
'division': fixture['division']
}
return jsonify(fixture_data)
else:
return jsonify({
'success': False,
'message': 'No upcoming fixtures found for HKFC C'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'Error fetching fixture: {str(e)}'
})
@app.route('/admin/api/clubs')
@basic_auth.required
def get_hockey_clubs():
"""API endpoint to fetch clubs from Hockey Hong Kong website"""
try:
clubs = get_hk_hockey_clubs()
if clubs:
# Format the club data for JSON response
club_data = {
'success': True,
'clubs': clubs,
'count': len(clubs)
}
return jsonify(club_data)
else:
return jsonify({
'success': False,
'message': 'No clubs found on Hockey Hong Kong website'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'Error fetching clubs: {str(e)}'
})
@app.route('/admin/api/import-clubs', methods=['POST'])
@basic_auth.required
def import_clubs():
"""API endpoint to import clubs from Hockey Hong Kong website"""
try:
clubs = get_hk_hockey_clubs()
imported_count = 0
updated_count = 0
for club in clubs:
club_name = club['name']
abbreviation = club.get('abbreviation', '')
# Check if club already exists
sql_check = text("SELECT id FROM clubs WHERE hockey_club = :club_name")
existing = sql_read(sql_check, {'club_name': club_name})
if existing:
# Update existing club
sql_update = text("UPDATE clubs SET logo_url = :logo_url WHERE hockey_club = :club_name")
logo_url = f"/static/images/clubs/{club_name.lower().replace(' ', '_').replace('.', '').replace(',', '')}_logo.png"
sql_write(sql_update, {'logo_url': logo_url, 'club_name': club_name})
updated_count += 1
else:
# Insert new club
sql_insert = text("INSERT INTO clubs (hockey_club, logo_url) VALUES (:club_name, :logo_url)")
logo_url = f"/static/images/clubs/{club_name.lower().replace(' ', '_').replace('.', '').replace(',', '')}_logo.png"
sql_write(sql_insert, {'club_name': club_name, 'logo_url': logo_url})
imported_count += 1
return jsonify({
'success': True,
'message': f'Successfully imported {imported_count} new clubs and updated {updated_count} existing clubs',
'imported': imported_count,
'updated': updated_count,
'total': len(clubs)
})
except Exception as e:
return jsonify({
'success': False,
'message': f'Error importing clubs: {str(e)}'
})
@app.route('/admin/api/match-opponent/<opponent>')
@basic_auth.required
def match_opponent(opponent):
"""API endpoint to match an opponent team to a club in the database"""
try:
club_info = get_opponent_club_info(opponent)
if club_info:
return jsonify({
'success': True,
'opponent': opponent,
'club_info': club_info
})
else:
return jsonify({
'success': False,
'message': f'No club found for opponent: {opponent}'
})
except Exception as e:
return jsonify({
'success': False,
'message': f'Error matching opponent: {str(e)}'
})
@app.route('/admin/api/team/<club>')
def admin_team_lookup(club):
"""API endpoint for team lookup by club"""
sql = "SELECT team FROM _clubTeams WHERE club='" + club + "'"
myteams = sql_read(sql)
return jsonify(myteams)
@app.route('/admin/api/logo/<club>')
def get_logo(club):
"""API endpoint for club logo lookup"""
sql = "SELECT logoURL FROM mensHockeyClubs WHERE hockeyClub='" + club + "'"
clubLogo = sql_read(sql)
return jsonify(clubLogo)
@app.route('/admin/api/fixture/<fixture>')
def admin_fixture_lookup(fixture):
"""API endpoint for fixture team lookup"""
sql = "SELECT homeTeam, awayTeam FROM hockeyFixtures WHERE fixtureNumber='" + fixture + "'"
myteams = sql_read(sql)
if not myteams:
return jsonify({'error': 'Fixture not found'})
if myteams[0]['homeTeam'].startswith("HKFC"):
nextOppo = myteams[0]['awayTeam']
else:
nextOppo = myteams[0]['homeTeam']
return jsonify(nextOppo)
@app.route('/admin/api/fixture/<fixture>/logo')
def admin_fixture_logo_lookup(fixture):
"""API endpoint for fixture logo lookup"""
sql = "SELECT homeTeam, awayTeam FROM hockeyFixtures WHERE fixtureNumber='" + fixture + "'"
myteams = sql_read(sql)
if not myteams:
return jsonify({'error': 'Fixture not found'})
if myteams[0]['homeTeam'].startswith("HKFC C"):
nextOppo = myteams[0]['awayTeam']
else:
nextOppo = myteams[0]['homeTeam']
sql2 = "SELECT club FROM _clubTeams WHERE displayName ='" + nextOppo + "'"
clubs = sql_read_static(sql2)
if not clubs:
return jsonify({'error': 'Club not found'})
clubName = clubs[0]['club']
sql3 = "SELECT logoUrl FROM mensHockeyClubs WHERE hockeyClub ='" + clubName + "'"
logo = sql_read_static(sql3)
if not logo:
return jsonify({'error': 'Logo not found'})
clubLogo = logo[0]['logoUrl']
return jsonify(clubLogo)
@app.route('/api/vote-results')
def vote_results():
"""API endpoint for voting results"""
# Get the current match date from admin settings
sql_date = text("SELECT nextdate FROM motmadminsettings WHERE userid = 'admin'")
date_result = sql_read_static(sql_date)
if not date_result:
return json.dumps([])
_matchDate = str(date_result[0]['nextdate']).replace('-', '')
print(f"Match date: {_matchDate}")
# Query for votes with the current match date
sql = text(f"SELECT playername, motm_{_matchDate}, dotd_{_matchDate} FROM _hkfc_c_motm WHERE (motm_{_matchDate} > 0) OR (dotd_{_matchDate} > 0)")
print(f"SQL: {sql}")
rows = sql_read(sql)
print(f"Results: {rows}")
# Format the results for the chart
formatted_results = []
for row in rows:
formatted_results.append({
'playerName': row['playername'],
f'motm_{_matchDate}': row[f'motm_{_matchDate}'],
f'dotd_{_matchDate}': row[f'dotd_{_matchDate}']
})
return jsonify(formatted_results)
@app.route('/api/poty-results')
def poty_results():
"""API endpoint for Player of the Year results"""
sql = text("SELECT playername, motmtotal, dotdtotal FROM _hkfc_c_motm WHERE (motmtotal > 0) OR (dotdtotal > 0)")
print(f"SQL: {sql}")
rows = sql_read(sql)
print(f"Results: {rows}")
return jsonify(rows)
@app.route('/admin/voting')
@basic_auth.required
def voting_chart():
"""Admin page for viewing voting charts"""
# Get the current match date from admin settings
sql_date = text("SELECT nextdate FROM motmadminsettings WHERE userid = 'admin'")
date_result = sql_read_static(sql_date)
if date_result:
matchDate = str(date_result[0]['nextdate']).replace('-', '')
else:
matchDate = '20251012' # Default fallback
return render_template('vote_chart.html', _matchDate=matchDate)
@app.route('/admin/poty')
@basic_auth.required
def poty_chart():
"""Admin page for Player of the Year chart"""
return render_template('poty_chart.html')
# ==================== DATABASE SETUP SECTION ====================
@app.route('/admin/database-setup', methods=['GET', 'POST'])
@basic_auth.required
def database_setup():
"""Admin page for database setup and configuration"""
form = DatabaseSetupForm()
if request.method == 'POST':
if form.test_connection.data:
# Test database connection
form_data = {
'database_type': form.database_type.data,
'sqlite_database_path': form.sqlite_database_path.data,
'mysql_host': form.mysql_host.data,
'mysql_port': form.mysql_port.data,
'mysql_database': form.mysql_database.data,
'mysql_username': form.mysql_username.data,
'mysql_password': form.mysql_password.data,
'mysql_charset': form.mysql_charset.data,
'postgres_host': form.postgres_host.data,
'postgres_port': form.postgres_port.data,
'postgres_database': form.postgres_database.data,
'postgres_username': form.postgres_username.data,
'postgres_password': form.postgres_password.data,
}
success, message = db_config_manager.test_connection(form_data)
if success:
flash(f'{message}', 'success')
else:
flash(f'{message}', 'error')
elif form.save_config.data:
# Save configuration
form_data = {
'database_type': form.database_type.data,
'sqlite_database_path': form.sqlite_database_path.data,
'mysql_host': form.mysql_host.data,
'mysql_port': form.mysql_port.data,
'mysql_database': form.mysql_database.data,
'mysql_username': form.mysql_username.data,
'mysql_password': form.mysql_password.data,
'mysql_charset': form.mysql_charset.data,
'postgres_host': form.postgres_host.data,
'postgres_port': form.postgres_port.data,
'postgres_database': form.postgres_database.data,
'postgres_username': form.postgres_username.data,
'postgres_password': form.postgres_password.data,
}
try:
db_config_manager.save_config(form_data)
flash('✅ Database configuration saved successfully!', 'success')
except Exception as e:
flash(f'❌ Failed to save configuration: {str(e)}', 'error')
elif form.initialize_database.data:
# Initialize database
try:
success, message = db_config_manager.initialize_database(
create_sample_data=form.create_sample_data.data
)
if success:
flash(f'{message}', 'success')
else:
flash(f'{message}', 'error')
except Exception as e:
flash(f'❌ Database initialization failed: {str(e)}', 'error')
# Load current configuration for display
current_config = db_config_manager.get_config_dict()
# Populate form with current configuration (only for GET requests or after POST processing)
for field_name, value in current_config.items():
if hasattr(form, field_name):
getattr(form, field_name).data = value
return render_template('database_setup.html', form=form, current_config=current_config)
@app.route('/admin/database-status')
@basic_auth.required
def database_status():
"""Admin page showing current database status and configuration"""
try:
# Reload database configuration to get latest settings
from database import DatabaseConfig
current_db_config = DatabaseConfig()
engine = current_db_config.engine
# Test connection
with engine.connect() as conn:
result = conn.execute(text("SELECT 1"))
result.fetchone()
connection_status = "✅ Connected"
# Get database info
db_info = {
'database_url': str(current_db_config.database_url),
'database_type': os.getenv('DATABASE_TYPE', 'unknown'),
'connection_status': connection_status
}
# Try to get table count
try:
from database import Base
table_count = len(Base.metadata.tables)
db_info['table_count'] = table_count
except:
db_info['table_count'] = 'Unknown'
except Exception as e:
db_info = {
'database_url': 'Not configured',
'database_type': 'Unknown',
'connection_status': f'❌ Connection failed: {str(e)}',
'table_count': 'Unknown'
}
return render_template('database_status.html', db_info=db_info)
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000, debug=True)