gcp-hockey-results/motm_app/main.py
2025-09-29 22:50:42 +08:00

995 lines
47 KiB
Python

# encoding=utf-8
import pymysql
import os
import json
import hashlib, uuid
import datetime
from datetime import datetime
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
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 db_setup import db_config_manager
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')
# ==================== 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 = "SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _hkfcC_matchSquad ORDER BY RAND()"
sql2 = "SELECT nextClub, nextTeam, nextDate, oppoLogo, hkfcLogo, currMotM, currDotD, nextFixture 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
sql3 = "SELECT hockeyResults2021.hockeyFixtures.date, hockeyResults.motmAdminSettings.nextFixture FROM hockeyResults2021.hockeyFixtures INNER JOIN hockeyResults.motmAdminSettings ON hockeyResults2021.hockeyFixtures.fixtureNumber = hockeyResults.motmAdminSettings.nextFixture"
nextMatchDate = sql_read(sql3)
if not nextMatchDate:
return render_template('error.html', message="No fixtures found. Please initialize the database with sample data.")
nextDate = nextMatchDate[0]['date']
formatDate = datetime.strftime(nextDate, '%A, %d %B %Y')
sql3 = "SELECT playerPictureURL FROM _HKFC_players INNER JOIN hockeyResults.motmAdminSettings ON _HKFC_players.playerNumber=hockeyResults.motmAdminSettings.currMotM"
sql4 = "SELECT playerPictureURL FROM _HKFC_players INNER JOIN hockeyResults.motmAdminSettings ON _HKFC_players.playerNumber=hockeyResults.motmAdminSettings.currDotD"
motm = sql_read(sql3)
dotd = sql_read(sql4)
# Handle empty results
if not motm or not dotd:
return render_template('error.html', message="Player data not found. Please initialize the database with sample data.")
motmURL = motm[0]['playerPictureURL']
dotdURL = dotd[0]['playerPictureURL']
sql5 = "SELECT comment FROM _motmComments INNER JOIN hockeyResults.motmAdminSettings ON _motmComments.matchDate=hockeyResults.motmAdminSettings.nextDate ORDER BY RAND() LIMIT 1"
comment = sql_read(sql5)
if comment == "":
comment = "No comments added yet"
form = motmForm()
sql6 = "SELECT motmUrlSuffix FROM hockeyResults.motmAdminSettings WHERE userid='admin'"
urlSuff = sql_read_static(sql6)
if not urlSuff:
return render_template('error.html', message="Admin settings not found. Please initialize the database.")
randomSuff = urlSuff[0]['motmUrlSuffix']
print(randomSuff)
if randomSuff == randomUrlSuffix:
return render_template('motm_vote.html', data=rows, comment=comment, formatDate=formatDate, matchNumber=nextFixture, oppo=oppo, hkfcLogo=hkfcLogo, oppoLogo=oppoLogo, dotdURL=dotdURL, motmURL=motmURL, form=form)
else:
return render_template('error.html')
@app.route('/motm/comments', methods=['GET', 'POST'])
def match_comments():
"""Display and allow adding match comments"""
sql = "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 = "INSERT INTO _motmComments (matchDate, opposition, comment) VALUES ('" + commentDate + "', '" + _oppo + "', '" + _fixed_comment + "')"
sql_write(sql3)
sql = "SELECT comment FROM _motmComments WHERE matchDate='" + _matchDate + "' ORDER BY RAND()"
comments = sql_read(sql)
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':
sql = "INSERT INTO _hkfc_c_motm (playerNumber, playerName, motmTotal, motm_" + _matchDate + ") SELECT playerNumber, playerNickname, '1', '1' FROM _HKFC_players WHERE playerNumber='" + _motm + "' ON DUPLICATE KEY UPDATE motmTotal = motmTotal + 1, motm_" + _matchDate + " = motm_" + _matchDate + " + 1"
sql2 = "INSERT INTO _hkfc_c_motm (playerNumber, playerName, dotdTotal, dotd_" + _matchDate + ") SELECT playerNumber, playerNickname, '1', '1' FROM _HKFC_players WHERE playerNumber='" + _dotd + "' ON DUPLICATE KEY UPDATE dotdTotal = dotdTotal + 1, dotd_" + _matchDate + " = dotd_" + _matchDate + " + 1"
if _comments == "":
print("No comment")
elif _comments == "Optional comments added here":
print("No comment")
else:
sql3 = "INSERT INTO _motmComments (_matchDate, opposition, comment) VALUES ('" + _matchDate + "', '" + _oppo + "', '" + _fixed_comments + "')"
sql_write(sql3)
sql_write(sql)
sql_write(sql2)
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['nextOppoTeam']
_nextMatchDate = request.form['nextMatchDate']
_currMotM = request.form['currMotM']
_currDotD = request.form['currDotD']
sql1 = "SELECT club FROM _clubTeams WHERE displayName='" + _nextTeam + "'"
_nextClubName = sql_read_static(sql1)
if not _nextClubName:
flash('Error: Club not found for team ' + _nextTeam, 'error')
return redirect(url_for('motm_admin'))
_nextClub = _nextClubName[0]['club']
sql = "UPDATE motmAdminSettings SET nextDate='" + _nextMatchDate + "', nextClub='" + _nextClub + "', nextTeam='" + _nextTeam + "', currMotM=" + _currMotM + ", currDotD=" + _currDotD + ""
sql_write_static(sql)
sql2 = "UPDATE motmAdminSettings INNER JOIN mensHockeyClubs ON motmAdminSettings.nextClub = mensHockeyClubs.hockeyClub SET motmAdminSettings.oppoLogo = mensHockeyClubs.logoURL WHERE mensHockeyClubs.hockeyClub='" + _nextClub + "'"
sql_write_static(sql2)
if form.saveButton.data:
flash('Settings saved!')
urlSuffix = randomUrlSuffix(8)
print(urlSuffix)
sql3 = "UPDATE motmAdminSettings SET motmUrlSuffix='" + urlSuffix + "' WHERE userid='admin'"
sql_write_static(sql3)
flash('MotM URL https://hockey.ervine.cloud/motm/'+urlSuffix)
elif form.activateButton.data:
# Generate a fixture number based on the date
_nextFixture = _nextMatchDate.replace('-', '')
sql4 = "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)
sql5 = "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://hockey.ervine.cloud/motm/'+currSuffix)
else:
flash('Something went wrong - check with Smithers')
# Load current settings to populate the form
sql_current = "SELECT nextDate FROM motmAdminSettings WHERE userid='admin'"
current_settings = sql_read_static(sql_current)
if current_settings:
from datetime import datetime
try:
current_date = datetime.strptime(current_settings[0]['nextDate'], '%Y-%m-%d').date()
form.nextMatchDate.data = current_date
except:
pass
sql4 = "SELECT hockeyClub FROM mensHockeyClubs ORDER BY hockeyClub"
sql5 = "SELECT nextClub, oppoLogo FROM motmAdminSettings"
sql6 = "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()
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()
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/squad/submit', methods=['POST'])
@basic_auth.required
def match_squad_submit():
"""Process squad selection"""
_playerNumbers = request.form.getlist('playerNumber')
for _playerNumber in _playerNumbers:
sql = "INSERT INTO _hkfcC_matchSquad (playerNumber, playerForenames, playerSurname, playerNickname) SELECT playerNumber, playerForenames, playerSurname, playerNickname FROM _HKFC_players WHERE playerNumber='" + _playerNumber + "'"
sql_write(sql)
sql2 = "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 = "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['playerNumber']
sql = "DELETE FROM _hkfcC_matchSquad WHERE playerNumber=" + _playerNumber + ""
sql_write(sql)
return render_template('player_removed.html', number=_playerNumber)
@app.route('/admin/squad/reset')
@basic_auth.required
def matchSquadReset():
"""Reset squad for new match"""
_matchNumber = str(mySettings('fixture'))
print(_matchNumber)
sql1 = "RENAME TABLE _hkfcC_matchSquad TO _hkfcC_matchSquad_" + _matchNumber + ""
sql2 = "CREATE TABLE _hkfcC_matchSquad (playerNumber smallint UNIQUE, playerForenames varchar(50), playerSurname varchar(30), playerNickname varchar(30) NOT NULL, PRIMARY KEY (playerNumber))"
sql3 = "UPDATE motmAdminSettings SET prevFixture='" + _matchNumber + "'"
sql_write(sql1)
sql_write(sql2)
sql_write_static(sql3)
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/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"""
_matchDate = str(mySettings('fixture'))
print(_matchDate)
sql = "SELECT playerName, motm_" + _matchDate + ", dotd_" + _matchDate + " FROM _hkfc_c_motm WHERE (motm_" + _matchDate + " > '0') OR (dotd_" + _matchDate + " > '0')"
print(sql)
rows = sql_read(sql)
print(rows)
return json.dumps(rows)
@app.route('/api/poty-results')
def poty_results():
"""API endpoint for Player of the Year results"""
sql = "SELECT playerName, motmTotal, dotdTotal FROM _hkfc_c_motm WHERE (motmTotal > '0') OR (dotdTotal > '0')"
print(sql)
rows = sql_read(sql)
return json.dumps(rows)
@app.route('/admin/voting')
@basic_auth.required
def voting_chart():
"""Admin page for viewing voting charts"""
matchDate = mySettings('fixture')
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)