feat: major rewrite of the webserver
gets rid of __init__ and runserver in favor of new modular design also introduces db model, first api endpoints, as well as their wrappers
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@@ -364,6 +364,13 @@ FodyWeavers.xsd
|
|||||||
|
|
||||||
# Wirtualne środowisko pythona
|
# Wirtualne środowisko pythona
|
||||||
FlaskWebProject/env
|
FlaskWebProject/env
|
||||||
|
.venv
|
||||||
|
|
||||||
# Wersja pythona
|
# Wersja pythona
|
||||||
FlaskWebProject/FlaskWebProject.pyproj
|
FlaskWebProject/FlaskWebProject.pyproj
|
||||||
|
|
||||||
|
# Baza sqlite
|
||||||
|
FlaskWebProject/FlaskWebProject/instance
|
||||||
|
|
||||||
|
# Poufne dane
|
||||||
|
config.toml
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
from flask import Flask, render_template
|
|
||||||
from .lewy_globals import *
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
stats = {
|
|
||||||
'goals': 38,
|
|
||||||
'assists': 12,
|
|
||||||
'matches': 45,
|
|
||||||
'matches_list': [
|
|
||||||
{'date': '2024-10-12', 'opponent': 'Real Madrid', 'goals': 2, 'assists': 1, 'minutes': 90},
|
|
||||||
{'date': '2024-10-19', 'opponent': 'Valencia', 'goals': 1, 'assists': 0, 'minutes': 85},
|
|
||||||
# Możesz dodać więcej meczów...
|
|
||||||
]
|
|
||||||
}
|
|
||||||
return render_template('index.html', goals=stats['goals'], assists=stats['assists'],
|
|
||||||
matches=stats['matches'], matches_list=stats['matches_list'],
|
|
||||||
commit_in_html=lewy_globals.getCommitInFormattedHTML())
|
|
||||||
|
|
||||||
@app.route('/mecze')
|
|
||||||
def mecze():
|
|
||||||
# Możesz dostarczyć szczegóły dotyczące meczów
|
|
||||||
matches = [
|
|
||||||
{'date': '2024-10-12', 'opponent': 'Real Madrid', 'goals': 2, 'assists': 1, 'minutes': 90},
|
|
||||||
{'date': '2024-10-19', 'opponent': 'Valencia', 'goals': 1, 'assists': 0, 'minutes': 85},
|
|
||||||
]
|
|
||||||
return render_template('matches.html', matches=matches)
|
|
||||||
|
|
||||||
@app.route('/statystyki')
|
|
||||||
def statystyki():
|
|
||||||
stats = {
|
|
||||||
'goals': 38,
|
|
||||||
'assists': 12,
|
|
||||||
'matches': 45,
|
|
||||||
}
|
|
||||||
return render_template('stats.html', stats=stats)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
app.run(debug=True)
|
|
||||||
12
FlaskWebProject/FlaskWebProject/config.example.toml
Normal file
12
FlaskWebProject/FlaskWebProject/config.example.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[general]
|
||||||
|
db_path_url = "postgresql+psycopg2://user:password@hostname/database_name"
|
||||||
|
db_prefix = "" # What (if any) prefix will be appended to table names.
|
||||||
|
is_proxied = false # Will ignore discrepancies between retrieved IP and public-facing URL.
|
||||||
|
public_facing_url = "http://127.0.0.1:5000/" # Used for URL rewriting. Note the trailing forward slash /.
|
||||||
|
|
||||||
|
[api]
|
||||||
|
# Leave empty to automatically generate API key every launch (insecure).
|
||||||
|
api_key = ""
|
||||||
|
|
||||||
|
[scraper]
|
||||||
|
user-agent = "" # Leave empty for default (Firefox ESR).
|
||||||
15
FlaskWebProject/FlaskWebProject/fs_scraper.py
Normal file
15
FlaskWebProject/FlaskWebProject/fs_scraper.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
class scraper:
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
'x-fsign': 'SW9D1eZo'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def pobierzDaneNajlepszegoSportowcaNaSwiecie() -> dict:
|
||||||
|
response = requests.get('https://3.flashscore.ninja/3/x/feed/plm_MVC8zHZD_0', headers=headers)
|
||||||
|
return json.loads(response.text)
|
||||||
168
FlaskWebProject/FlaskWebProject/lewy.py
Normal file
168
FlaskWebProject/FlaskWebProject/lewy.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
from argparse import ArgumentParser
|
||||||
|
from flask import Flask, Response, render_template
|
||||||
|
from flask_apscheduler import APScheduler
|
||||||
|
from lewy_globals import colors as c
|
||||||
|
import lewy_api
|
||||||
|
import lewy_db
|
||||||
|
import lewy_globals
|
||||||
|
import lewy_routes
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app_host = "None"
|
||||||
|
app_port = "None"
|
||||||
|
|
||||||
|
def setup():
|
||||||
|
# sanity check: make sure config is set
|
||||||
|
# required to make `flask --app lewy run --debug` work
|
||||||
|
global config, app_host, app_port
|
||||||
|
try:
|
||||||
|
if not config['general']:
|
||||||
|
lewy_globals.setConfig(lewy_globals.configfile)
|
||||||
|
config = lewy_globals.config
|
||||||
|
except:
|
||||||
|
lewy_globals.setConfig(lewy_globals.configfile)
|
||||||
|
config = lewy_globals.config
|
||||||
|
|
||||||
|
# setting all the variables
|
||||||
|
lewy_globals.starttime = int(time.time())
|
||||||
|
lewy_globals.realUptime = 0
|
||||||
|
lewy_globals.apiRequests = 0
|
||||||
|
lewy_globals.apiFailedRequests = 0
|
||||||
|
lewy_globals.isProxied = config['general']['is_proxied']
|
||||||
|
lewy_globals.outsideApiHits = 0
|
||||||
|
|
||||||
|
are_we_sure_of_host_and_port = True
|
||||||
|
if app_host == "None":
|
||||||
|
app_host = "127.0.0.1"
|
||||||
|
are_we_sure_of_host_and_port = False
|
||||||
|
if app_port == "None":
|
||||||
|
app_port = "5000"
|
||||||
|
are_we_sure_of_host_and_port = False
|
||||||
|
|
||||||
|
public_facing_url = config['general']['public_facing_url']
|
||||||
|
if len(public_facing_url) >= 4 and public_facing_url[0:5].lower() == "https":
|
||||||
|
https_str = f"{c.OKNLUE}INFO: {c.ENDC} You're trying to run this web server on HTTPS, but currently it's not possible to do that!\n"
|
||||||
|
https_str += f" Please consider running this service behind a reverse proxy if you need HTTPS.\n"
|
||||||
|
print(https_str)
|
||||||
|
rewrite_sanity_check = public_facing_url.replace(f"{app_host}:{app_port}", "")
|
||||||
|
if not config['general']['is_proxied'] and public_facing_url == rewrite_sanity_check:
|
||||||
|
sanity_string = f"{c.OKBLUE}INFO:{c.ENDC} Public facing URL does not match the IP and port the server is running on.\n"
|
||||||
|
sanity_string += f" Expected: {c.OKCYAN}{config['general']['public_facing_url']}{c.ENDC}, but"
|
||||||
|
if not are_we_sure_of_host_and_port: sanity_string += " (assuming it's)"
|
||||||
|
sanity_string += f" running on: {c.OKCYAN}{app_host}:{app_port}{c.ENDC}.\n"
|
||||||
|
sanity_string += f" This is just a sanity check and may not neccessarily mean bad configuration.\n"
|
||||||
|
sanity_string += f" If you're running a reverse proxy, set {c.OKCYAN}is_proxied{c.ENDC} to true to silence this message.\n"
|
||||||
|
print(sanity_string)
|
||||||
|
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = f"{config['general']['db_path_url']}"
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
|
||||||
|
# Widoki widoczne dla "normalnego" użytkownika:
|
||||||
|
app.add_url_rule('/', view_func=lewy_routes.index)
|
||||||
|
app.add_url_rule('/index.html', view_func=lewy_routes.index)
|
||||||
|
app.add_url_rule('/mecze', view_func=lewy_routes.mecze)
|
||||||
|
app.add_url_rule('/statystyki', view_func=lewy_routes.statystyki)
|
||||||
|
app.add_url_rule('/toggle_dark_mode', view_func=lewy_routes.toggle_dark_mode)
|
||||||
|
|
||||||
|
# API:
|
||||||
|
app.add_url_rule('/api/', view_func=lewy_api.api_greeting)
|
||||||
|
app.add_url_rule('/api/<path:received_request>', view_func=lewy_api.api_global_catchall)
|
||||||
|
|
||||||
|
db = lewy_db.initDB(app, config)
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.create_all()
|
||||||
|
|
||||||
|
# job scheduler for repetetive tasks
|
||||||
|
scheduler = APScheduler()
|
||||||
|
scheduler.add_job(func=every5seconds, trigger='interval', id='5sec', seconds=5)
|
||||||
|
scheduler.add_job(func=every2hours, trigger='interval', id='2hr', hours=2)
|
||||||
|
scheduler.start()
|
||||||
|
|
||||||
|
# gets called every 5 seconds
|
||||||
|
def every5seconds():
|
||||||
|
# update the "real" uptime counter
|
||||||
|
lewy_globals.realUptime += 5
|
||||||
|
|
||||||
|
def every2hours():
|
||||||
|
# zaktualizuj bazę danych scrapując FS
|
||||||
|
# ...
|
||||||
|
return
|
||||||
|
|
||||||
|
@app.route('/<string:val>', methods=['GET'])
|
||||||
|
def blank(val):
|
||||||
|
return Response(f"{val}: not implemented in lewangoalski {lewy_globals.getCommitWithFailsafe()}", mimetype="text/plain")
|
||||||
|
|
||||||
|
def main(args):
|
||||||
|
print(f"{c.BOLD + c.HEADER}Witaj w webaplikacji 'lewangoalski' ({lewy_globals.getCommitWithFailsafe()})!{c.ENDC}")
|
||||||
|
print(f"Aby uruchomić w trybie deweloperskim (aby włączyć automatyczne przeładowanie zmian), użyj: {c.OKCYAN}flask --app lewy run --debug{c.ENDC}.")
|
||||||
|
print( "Aby uruchomić lokalnie, użyj adresu IP 127.0.0.1. Aby uruchomić na każdym z interfejsów, użyj 0.0.0.0.\n")
|
||||||
|
|
||||||
|
global config, app_host, app_port
|
||||||
|
try:
|
||||||
|
# if specified, use custom config file
|
||||||
|
lewy_globals.configfile = args.config
|
||||||
|
lewy_globals.setConfig(lewy_globals.configfile)
|
||||||
|
|
||||||
|
except:
|
||||||
|
# if not, try using the default "config.toml"
|
||||||
|
if os.path.exists("config.toml"):
|
||||||
|
lewy_globals.configfile = "config.toml"
|
||||||
|
else:
|
||||||
|
# unless it's not there, if that's the case then use the dummy file
|
||||||
|
lewy_globals.configfile = ""
|
||||||
|
# but try to set the API secret if provided by the user
|
||||||
|
if args.secret:
|
||||||
|
lewy_globals.randomly_generated_passcode = args.secret
|
||||||
|
lewy_globals.setConfig(lewy_globals.configfile)
|
||||||
|
|
||||||
|
config = lewy_globals.config
|
||||||
|
|
||||||
|
try:
|
||||||
|
host = args.ip
|
||||||
|
port = args.port
|
||||||
|
if not host or not port:
|
||||||
|
raise Exception
|
||||||
|
except:
|
||||||
|
config_ip, config_port = lewy_globals.extractIpAndPortFromPublicUrl()
|
||||||
|
print(f"Wpisz nazwę_hosta:port, na których należy uruchomić serwer Flask [domyślnie: {config_ip}:{config_port}]:")
|
||||||
|
try:
|
||||||
|
host_port = input('> ').split(':')
|
||||||
|
if host_port == ['']:
|
||||||
|
host_port = [config_ip, config_port] # defaults
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print(" ...wychodzę z programu."), quit() # handle Ctrl+C
|
||||||
|
|
||||||
|
host = host_port[0]
|
||||||
|
port = host_port[1]
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
app_host = host
|
||||||
|
app_port = port
|
||||||
|
|
||||||
|
setup()
|
||||||
|
app.run(host=host, port=int(port))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
#app.run(host="127.0.0.1", port=5000)
|
||||||
|
#app.run(host="0.0.0.0", port=5000)
|
||||||
|
parser = ArgumentParser(description='Aplikacja webowa do śledzenia statystyk Roberta Lewandowskiego.')
|
||||||
|
|
||||||
|
parser.add_argument("-i", "--ip", dest="ip", help="ip address/interface to bind to")
|
||||||
|
parser.add_argument("-p", "--port", dest="port", help="port on which the flask web backend should be ran")
|
||||||
|
parser.add_argument("-c", "--config", dest="config", help="path to TOML config file")
|
||||||
|
parser.add_argument("-s", "--secret", dest="secret", help="API key for resource access") # NOT tested
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
main(args)
|
||||||
|
|
||||||
|
else:
|
||||||
|
app_host = os.getenv("FLASK_RUN_HOST", "None")
|
||||||
|
app_port = os.getenv("FLASK_RUN_PORT", "None")
|
||||||
|
setup()
|
||||||
43
FlaskWebProject/FlaskWebProject/lewy_api.py
Normal file
43
FlaskWebProject/FlaskWebProject/lewy_api.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from flask import Response, request
|
||||||
|
from lewy_globals import colors as c
|
||||||
|
from markupsafe import escape
|
||||||
|
import json
|
||||||
|
import lewy_globals
|
||||||
|
import lewy_api_v1
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
def api_greeting():
|
||||||
|
string = {'status': 200, 'msg': f"ok (lewangoalski {lewy_globals.version})", 'latest_api': f"v{lewy_globals.apiVersion}"}
|
||||||
|
string = json.dumps(string)
|
||||||
|
return Response(string, mimetype='application/json')
|
||||||
|
|
||||||
|
def api_global_catchall(received_request):
|
||||||
|
lewy_globals.apiRequests += 1
|
||||||
|
if request.environ['REMOTE_ADDR'] != "127.0.0.1" or (lewy_globals.isProxied and request.environ['X-Forwarded-For'] != "127.0.0.1"):
|
||||||
|
lewy_globals.outsideApiHits += 1
|
||||||
|
|
||||||
|
request_list = received_request.split('/')
|
||||||
|
api_version = request_list[0]
|
||||||
|
if request_list[0] == 'v1':
|
||||||
|
# use v1 api
|
||||||
|
del request_list[0]
|
||||||
|
# if list is empty, aka /api/v1/, or /api/v1
|
||||||
|
if request_list == [''] or request_list == []:
|
||||||
|
resp = api_greeting()
|
||||||
|
try:
|
||||||
|
status, received, data = lewy_api_v1.lookup(request_list)
|
||||||
|
except Exception as e:
|
||||||
|
lewy_globals.apiFailedRequests += 1
|
||||||
|
stripped_filename = __file__[max(__file__.rfind("/"), __file__.rfind("\\")) + 1:]
|
||||||
|
print(f"\n{c.FAIL}Error! /api/{received_request} -> {stripped_filename}:L{e.__traceback__.tb_lineno} -> {type(e).__name__}{c.ENDC}:")
|
||||||
|
print(traceback.format_exc())
|
||||||
|
status, received, data = 500, f"internal server error: call ended in failure: {e} ({stripped_filename}:L{e.__traceback__.tb_lineno})", []
|
||||||
|
resp = Response(json.dumps({'status': status, 'msg': received, 'data': data}), mimetype='application/json', status=status)
|
||||||
|
else:
|
||||||
|
lewy_globals.apiFailedRequests += 1
|
||||||
|
status, received, data = 405, f'error: unsupported api version: "{request_list[0]}". try: "v{lewy_globals.apiVersion}".', []
|
||||||
|
resp = Response(json.dumps({'status': status, 'msg': received, 'data': data}), mimetype='application/json', status=status)
|
||||||
|
|
||||||
|
return resp
|
||||||
44
FlaskWebProject/FlaskWebProject/lewy_api_v1.py
Normal file
44
FlaskWebProject/FlaskWebProject/lewy_api_v1.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# API is expected to return:
|
||||||
|
# - HTTP status code,
|
||||||
|
# - human-readable status message,
|
||||||
|
# - json with appropriate data
|
||||||
|
import flask, json, time
|
||||||
|
import lewy_globals
|
||||||
|
|
||||||
|
def incrementBadRequests():
|
||||||
|
lewy_globals.apiFailedRequests += 1
|
||||||
|
|
||||||
|
def notImplemented(data):
|
||||||
|
# TODO: change list to string -> data, not data[0]
|
||||||
|
return 501, f"not recognised/implemented: {data[0]}", []
|
||||||
|
|
||||||
|
def stub_hello():
|
||||||
|
return 200, 'hello from v1! stats are at /api/v1/stats', []
|
||||||
|
|
||||||
|
def stats():
|
||||||
|
data_to_send = {
|
||||||
|
"start_time": lewy_globals.starttime,
|
||||||
|
"uptime": lewy_globals.getUptime(),
|
||||||
|
"real_uptime": lewy_globals.realUptime,
|
||||||
|
"total_api_requests": lewy_globals.apiRequests,
|
||||||
|
"failed_api_requests": lewy_globals.apiFailedRequests,
|
||||||
|
"outside_api_requests": lewy_globals.outsideApiHits,
|
||||||
|
"local_api_requests": lewy_globals.apiRequests - lewy_globals.outsideApiHits
|
||||||
|
}
|
||||||
|
return 200, "OK", data_to_send
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def lookup(data):
|
||||||
|
if data == []:
|
||||||
|
return stub_hello()
|
||||||
|
match data[0].lower():
|
||||||
|
case 'stats' | '':
|
||||||
|
return stats()
|
||||||
|
case 'user':
|
||||||
|
return stub_hello()
|
||||||
|
case 'info':
|
||||||
|
return stub_hello()
|
||||||
|
case _:
|
||||||
|
incrementBadRequests()
|
||||||
|
return notImplemented(data)
|
||||||
86
FlaskWebProject/FlaskWebProject/lewy_db.py
Normal file
86
FlaskWebProject/FlaskWebProject/lewy_db.py
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
import toml
|
||||||
|
|
||||||
|
global db
|
||||||
|
|
||||||
|
def initDB(app, config):
|
||||||
|
tablenameprefix = config['general']['db_prefix'] + "_lewangoalski_"
|
||||||
|
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
|
||||||
|
class sportowcy(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "sportowcy"
|
||||||
|
id_zawodnika = db.Column(db.Integer, primary_key=True)
|
||||||
|
data_urodzenia = db.Column(db.String(10))
|
||||||
|
czy_aktywny = db.Column(db.Boolean)
|
||||||
|
klub = db.Column(db.String(63))
|
||||||
|
narodowosc = db.Column(db.String(3))
|
||||||
|
ilosc_trofeow = db.Column(db.Integer)
|
||||||
|
ostatnie_trofeum = db.Column(db.Integer)
|
||||||
|
pierwszy_mecz = db.Column(db.Integer)
|
||||||
|
# ostatni_mecz = db.Column(db.Integer) # statystyki_sportowcow już to przechowuje
|
||||||
|
wycena = db.Column(db.BigInteger)
|
||||||
|
ostatni_gol_dla = db.Column(db.String(3))
|
||||||
|
statystyka = db.Column(db.Integer)
|
||||||
|
|
||||||
|
class trofea(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "trofea"
|
||||||
|
id_trofeum = db.Column(db.Integer, primary_key=True)
|
||||||
|
id_zawodnika = db.Column(db.Integer) # != None
|
||||||
|
nazwa = db.Column(db.String(127))
|
||||||
|
sezon = db.Column(db.String(9))
|
||||||
|
rok = db.Column(db.String(4))
|
||||||
|
|
||||||
|
class sportowcy_w_meczach(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "sportowcy_w_meczach"
|
||||||
|
id_rekordu = db.Column(db.Integer, primary_key=True)
|
||||||
|
id_zawodnika = db.Column(db.Integer) # != None
|
||||||
|
zewnetrzne_id_meczu = db.Column(db.Integer) # != None
|
||||||
|
czas_gry = db.Column(db.Integer)
|
||||||
|
goli = db.Column(db.Integer)
|
||||||
|
asyst = db.Column(db.Integer)
|
||||||
|
interwencje_bramkarza = db.Column(db.Integer)
|
||||||
|
suma_interwencji_na_bramke = db.Column(db.Integer)
|
||||||
|
zolte_kartki = db.Column(db.Integer)
|
||||||
|
czerwone_kartki = db.Column(db.Integer)
|
||||||
|
wygrana = db.Column(db.Integer)
|
||||||
|
wynik = db.Column(db.Float)
|
||||||
|
|
||||||
|
class statystyki_sportowcow(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "statystyki_sportowcow"
|
||||||
|
id_statystyki = db.Column(db.Integer, primary_key=True)
|
||||||
|
ostatni_mecz = db.Column(db.Integer)
|
||||||
|
ilosc_wystapien = db.Column(db.Integer)
|
||||||
|
minut_gry = db.Column(db.BigInteger)
|
||||||
|
gier_sum = db.Column(db.Integer)
|
||||||
|
goli_sum = db.Column(db.Integer)
|
||||||
|
asyst_sum = db.Column(db.Integer)
|
||||||
|
interwencji_sum = db.Column(db.Integer)
|
||||||
|
nieobronionych_interwencji_sum = db.Column(db.Integer)
|
||||||
|
zoltych_kartek_sum = db.Column(db.Integer)
|
||||||
|
czerwonych_kartek_sum = db.Column(db.Integer)
|
||||||
|
wygranych_sum = db.Column(db.Integer)
|
||||||
|
wynik_sum = db.Column(db.Integer)
|
||||||
|
meczow_do_wynikow_sum = db.Column(db.Integer)
|
||||||
|
|
||||||
|
class kluby(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "kluby"
|
||||||
|
id_klubu = db.Column(db.String(63), primary_key=True)
|
||||||
|
pelna_nazwa = db.Column(db.String(63))
|
||||||
|
skrocona_nazwa = db.Column(db.String(3))
|
||||||
|
|
||||||
|
class mecze(db.Model):
|
||||||
|
__tablename__ = tablenameprefix + "mecze"
|
||||||
|
id_meczu = db.Column(db.Integer, primary_key=True)
|
||||||
|
zewnetrzne_id_meczu = db.Column(db.String(15)) # != None
|
||||||
|
data = db.Column(db.DateTime)
|
||||||
|
gospodarze = db.Column(db.String(3))
|
||||||
|
goscie = db.Column(db.String(3))
|
||||||
|
gosp_wynik = db.Column(db.Integer)
|
||||||
|
gosc_wynik = db.Column(db.Integer)
|
||||||
|
sezon = db.Column(db.String(9))
|
||||||
|
nazwa_turnieju = db.Column(db.String(127))
|
||||||
|
skrocona_nazwa_turnieju = db.Column(db.String(15))
|
||||||
|
flaga = db.Column(db.Integer)
|
||||||
|
|
||||||
|
return db
|
||||||
@@ -1,9 +1,37 @@
|
|||||||
from git import Repo # hash ostatniego commitu
|
from git import Repo # hash ostatniego commitu
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import toml
|
||||||
|
|
||||||
|
global config, randomly_generated_passcode
|
||||||
|
|
||||||
|
class colors:
|
||||||
|
HEADER = '\033[95m'
|
||||||
|
OKBLUE = '\033[94m'
|
||||||
|
OKCYAN = '\033[96m'
|
||||||
|
OKGREEN = '\033[92m'
|
||||||
|
WARNING = '\033[93m'
|
||||||
|
FAIL = '\033[91m'
|
||||||
|
ENDC = '\033[0m'
|
||||||
|
BOLD = '\033[1m'
|
||||||
|
UNDERLINE = '\033[4m'
|
||||||
|
ENDL = '\n'
|
||||||
|
|
||||||
|
def safeTraverse(obj: dict, path: list, default=None):
|
||||||
|
result = obj
|
||||||
|
try:
|
||||||
|
for x in path:
|
||||||
|
result = result[x]
|
||||||
|
except KeyError:
|
||||||
|
result = default
|
||||||
|
# print(f"error reading: {' -> '.join(path)} - returning: {default}")
|
||||||
|
finally:
|
||||||
|
return result
|
||||||
|
|
||||||
def getCommit():
|
def getCommit():
|
||||||
try:
|
try:
|
||||||
return Repo(search_parent_directories=True).head.object.hexsha
|
return Repo(search_parent_directories=True).head.object.hexsha
|
||||||
except:
|
except Exception as e:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def getCommitInFormattedHTML():
|
def getCommitInFormattedHTML():
|
||||||
@@ -13,4 +41,85 @@ def getCommitInFormattedHTML():
|
|||||||
if commit is not None:
|
if commit is not None:
|
||||||
repo = f"<p>Commit: <a href='https://gitea.7o7.cx/roberteam/lewangoalski/commit/{commit}'>{commit[:11]}</a></p>"
|
repo = f"<p>Commit: <a href='https://gitea.7o7.cx/roberteam/lewangoalski/commit/{commit}'>{commit[:11]}</a></p>"
|
||||||
|
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
def getCommitWithFailsafe():
|
||||||
|
commit = getCommit()
|
||||||
|
|
||||||
|
if commit is None:
|
||||||
|
commit = "(unknown commit)"
|
||||||
|
else:
|
||||||
|
commit = "#" + commit
|
||||||
|
|
||||||
|
return commit[:12]
|
||||||
|
|
||||||
|
def ensureRandomlyGeneratedPassword():
|
||||||
|
global randomly_generated_passcode
|
||||||
|
|
||||||
|
# iff the passcode is 0, as we manually set it elsewhere!
|
||||||
|
if randomly_generated_passcode == 0:
|
||||||
|
# generate a pseudorandom one and use it in the temporary config
|
||||||
|
randomly_generated_passcode = str(int(time.time() * 1337 % 899_999 + 100_000))
|
||||||
|
|
||||||
|
print(f"{colors.WARNING}WARNING{colors.ENDC}: Default config populated with one-time, insecure pseudorandom API key: {colors.OKCYAN}{randomly_generated_passcode}{colors.ENDC}.\n"
|
||||||
|
f" The API key is not the Flask debugger PIN. You need to provide a config file for persistence!{colors.ENDL}")
|
||||||
|
|
||||||
|
def getConfig(configfile):
|
||||||
|
global randomly_generated_passcode
|
||||||
|
|
||||||
|
if not os.path.exists(configfile):
|
||||||
|
dummy_config = {'general': {'db_path_url': 'sqlite:///lewangoalski.sqlite', 'is_proxied': False, 'public_facing_url': 'http://127.0.0.1:5000/', db_prefix: 'lewy_sqlite'}, 'api': {'api_key': 'CHANGEME'}, 'scraper': {'user-agent': ''}}
|
||||||
|
# if a passcode has not been provided by the user (config file doesn't exist, and user didn't specify it using an argument)
|
||||||
|
print(f"{colors.WARNING}WARNING{colors.ENDC}: Using default, baked in config data. {colors.ENDL}"
|
||||||
|
f" Consider copying and editing the provided example file ({colors.OKCYAN}config.example.toml{colors.ENDC}).")
|
||||||
|
|
||||||
|
ensureRandomlyGeneratedPassword()
|
||||||
|
|
||||||
|
dummy_config['api']['api_key'] = str(randomly_generated_passcode)
|
||||||
|
return dummy_config
|
||||||
|
|
||||||
|
else:
|
||||||
|
return toml.load(configfile)
|
||||||
|
|
||||||
|
def setConfig(configfile):
|
||||||
|
global config
|
||||||
|
config = getConfig(configfile)
|
||||||
|
|
||||||
|
if safeTraverse(config['api']['api_key'], []) is None or not config['api']['api_key']:
|
||||||
|
ensureRandomlyGeneratedPassword()
|
||||||
|
config['api']['api_key'] = str(randomly_generated_passcode)
|
||||||
|
|
||||||
|
|
||||||
|
def getHeaders():
|
||||||
|
# NOTE: use ESR user-agent
|
||||||
|
# user_agent = 'Mozilla/5.0 (Windows NT 10.0; rv:130.0) Gecko/20100101 Firefox/130.0'
|
||||||
|
user_agent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0'
|
||||||
|
|
||||||
|
if safeTraverse(config[scraper]['user-agent'], []) is not None:
|
||||||
|
user_agent = config[scraper]['user-agent']
|
||||||
|
|
||||||
|
return user_agent
|
||||||
|
|
||||||
|
def getUptime():
|
||||||
|
return int(time.time()) - starttime
|
||||||
|
|
||||||
|
def extractIpAndPortFromPublicUrl() -> tuple:
|
||||||
|
|
||||||
|
ip, port = "127.0.0.1", "5000"
|
||||||
|
|
||||||
|
try:
|
||||||
|
url = config['general']['public_facing_url'].replace(":/", "")
|
||||||
|
url_parts = url.split('/')
|
||||||
|
ip_and_port = url_parts[1]
|
||||||
|
ip, port = ip_and_port.split(':')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return ip, port
|
||||||
|
|
||||||
|
# Please leave at the bottom of this file.
|
||||||
|
config = {}
|
||||||
|
configfile = "config.toml"
|
||||||
|
version = getCommitWithFailsafe()
|
||||||
|
apiVersion = "1"
|
||||||
|
randomly_generated_passcode = 0
|
||||||
43
FlaskWebProject/FlaskWebProject/lewy_routes.py
Normal file
43
FlaskWebProject/FlaskWebProject/lewy_routes.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
from flask import render_template, request, make_response
|
||||||
|
import lewy_globals
|
||||||
|
|
||||||
|
def index():
|
||||||
|
dark_mode = request.cookies.get('darkMode', 'disabled')
|
||||||
|
stats = {
|
||||||
|
'goals': 38,
|
||||||
|
'assists': 12,
|
||||||
|
'matches': 45,
|
||||||
|
'matches_list': [
|
||||||
|
{'date': '2024-10-12', 'opponent': 'Real Madrid', 'goals': 2, 'assists': 1, 'minutes': 90},
|
||||||
|
{'date': '2024-10-19', 'opponent': 'Valencia', 'goals': 1, 'assists': 0, 'minutes': 85},
|
||||||
|
# Możesz dodać więcej meczów...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return render_template('index.html', goals=stats['goals'], assists=stats['assists'],
|
||||||
|
matches=stats['matches'], matches_list=stats['matches_list'],
|
||||||
|
commit_in_html=lewy_globals.getCommitInFormattedHTML(),
|
||||||
|
dark_mode=dark_mode)
|
||||||
|
|
||||||
|
def mecze():
|
||||||
|
# Możesz dostarczyć szczegóły dotyczące meczów
|
||||||
|
matches = [
|
||||||
|
{'date': '2024-10-12', 'opponent': 'Real Madrid', 'goals': 2, 'assists': 1, 'minutes': 90},
|
||||||
|
{'date': '2024-10-19', 'opponent': 'Valencia', 'goals': 1, 'assists': 0, 'minutes': 85},
|
||||||
|
]
|
||||||
|
return render_template('matches.html', matches=matches)
|
||||||
|
|
||||||
|
def statystyki():
|
||||||
|
stats = {
|
||||||
|
'goals': 38,
|
||||||
|
'assists': 12,
|
||||||
|
'matches': 45,
|
||||||
|
}
|
||||||
|
return render_template('stats.html', stats=stats)
|
||||||
|
|
||||||
|
def toggle_dark_mode():
|
||||||
|
# Przełącz tryb i zapisz w ciasteczku
|
||||||
|
dark_mode = request.cookies.get('darkMode', 'disabled')
|
||||||
|
new_mode = 'enabled' if dark_mode == 'disabled' else 'disabled'
|
||||||
|
response = make_response("OK")
|
||||||
|
response.set_cookie('darkMode', new_mode, max_age=31536000) # Ustawienie ciasteczka na 1 rok
|
||||||
|
return response
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
from flask import render_template, request, make_response
|
|
||||||
from FlaskWebProject import app
|
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
# Odczyt ciasteczka "darkMode" – domyślnie "disabled"
|
|
||||||
dark_mode = request.cookies.get('darkMode', 'disabled')
|
|
||||||
return render_template('index.html', dark_mode=dark_mode)
|
|
||||||
|
|
||||||
@app.route('/toggle_dark_mode')
|
|
||||||
def toggle_dark_mode():
|
|
||||||
# Przełącz tryb i zapisz w ciasteczku
|
|
||||||
dark_mode = request.cookies.get('darkMode', 'disabled')
|
|
||||||
new_mode = 'enabled' if dark_mode == 'disabled' else 'disabled'
|
|
||||||
response = make_response("OK")
|
|
||||||
response.set_cookie('darkMode', new_mode, max_age=31536000) # Ustawienie ciasteczka na 1 rok
|
|
||||||
return response
|
|
||||||
@@ -1,14 +1,5 @@
|
|||||||
"""
|
"""
|
||||||
This script runs the FlaskWebProject application using a development server.
|
Please see README.md for more tips on how to get your server running.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from os import environ
|
print("runserver.py is obsolete. Please run your server with lewy.py.")
|
||||||
from FlaskWebProject import app
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
HOST = environ.get('SERVER_HOST', 'localhost')
|
|
||||||
try:
|
|
||||||
PORT = int(environ.get('SERVER_PORT', '5555'))
|
|
||||||
except ValueError:
|
|
||||||
PORT = 5555
|
|
||||||
app.run(HOST, PORT)
|
|
||||||
Reference in New Issue
Block a user