first commit
This commit is contained in:
0
services/__init__.py
Normal file
0
services/__init__.py
Normal file
BIN
services/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
services/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/auth_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/auth_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/game_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/game_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/players_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/players_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/schedule_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/schedule_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/standings_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/standings_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/teams_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/teams_service.cpython-312.pyc
Normal file
Binary file not shown.
BIN
services/__pycache__/vmix_json_service.cpython-312.pyc
Normal file
BIN
services/__pycache__/vmix_json_service.cpython-312.pyc
Normal file
Binary file not shown.
155
services/auth_service.py
Normal file
155
services/auth_service.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import secrets
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
|
||||
from repositories.auth_repository import (
|
||||
create_auth_session_record,
|
||||
get_auth_session_by_token,
|
||||
revoke_auth_session,
|
||||
)
|
||||
|
||||
IDLE_TIMEOUT_SECONDS = 2 * 60 * 60
|
||||
SESSION_TOUCH_THROTTLE_SECONDS = 60
|
||||
PBKDF2_ITERATIONS = 260_000
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
salt = secrets.token_bytes(16)
|
||||
derived = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
password.encode("utf-8"),
|
||||
salt,
|
||||
PBKDF2_ITERATIONS,
|
||||
)
|
||||
return (
|
||||
f"pbkdf2_sha256${PBKDF2_ITERATIONS}$"
|
||||
f"{base64.b64encode(salt).decode()}$"
|
||||
f"{base64.b64encode(derived).decode()}"
|
||||
)
|
||||
|
||||
|
||||
def verify_password(password: str, stored_hash: str) -> bool:
|
||||
try:
|
||||
algorithm, iterations_raw, salt_b64, hash_b64 = stored_hash.split("$", 3)
|
||||
if algorithm != "pbkdf2_sha256":
|
||||
return False
|
||||
iterations = int(iterations_raw)
|
||||
salt = base64.b64decode(salt_b64)
|
||||
expected = base64.b64decode(hash_b64)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
actual = hashlib.pbkdf2_hmac(
|
||||
"sha256",
|
||||
password.encode("utf-8"),
|
||||
salt,
|
||||
iterations,
|
||||
)
|
||||
return hmac.compare_digest(actual, expected)
|
||||
|
||||
|
||||
def create_auth_session(
|
||||
user_id: int,
|
||||
ip_address: str | None = None,
|
||||
user_agent: str | None = None,
|
||||
) -> str:
|
||||
token = secrets.token_urlsafe(48)
|
||||
expires_at = datetime.now(timezone.utc) + timedelta(seconds=IDLE_TIMEOUT_SECONDS)
|
||||
create_auth_session_record(
|
||||
user_id=user_id,
|
||||
session_token=token,
|
||||
expires_at=expires_at,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
def _is_api_request(request: Request) -> bool:
|
||||
path = request.url.path
|
||||
if request.method in {"POST", "PUT", "PATCH", "DELETE"}:
|
||||
return True
|
||||
if path.endswith("/events") or "/event/" in path or path.endswith("/event"):
|
||||
return True
|
||||
accept = request.headers.get("accept", "")
|
||||
requested_with = request.headers.get("x-requested-with", "")
|
||||
return "application/json" in accept or requested_with.lower() == "xmlhttprequest"
|
||||
|
||||
|
||||
def build_not_authenticated_response(request: Request):
|
||||
if _is_api_request(request):
|
||||
return JSONResponse({"error": "auth_expired"}, status_code=401)
|
||||
return RedirectResponse(url="/login?reason=idle", status_code=303)
|
||||
|
||||
|
||||
def revoke_auth_session_by_request(request: Request):
|
||||
token = request.cookies.get("auth_token")
|
||||
if token:
|
||||
revoke_auth_session(token)
|
||||
|
||||
|
||||
def get_current_user_from_request(request: Request):
|
||||
token = request.cookies.get("auth_token")
|
||||
if not token:
|
||||
return None
|
||||
|
||||
row = get_auth_session_by_token(token)
|
||||
if not row:
|
||||
return None
|
||||
|
||||
(
|
||||
session_id,
|
||||
user_id,
|
||||
session_token,
|
||||
created_at,
|
||||
last_activity_at,
|
||||
expires_at,
|
||||
revoked_at,
|
||||
ip_address,
|
||||
user_agent,
|
||||
username,
|
||||
is_active,
|
||||
role,
|
||||
) = row
|
||||
|
||||
if revoked_at is not None or not is_active:
|
||||
return None
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
if last_activity_at.tzinfo is None:
|
||||
last_activity_at = last_activity_at.replace(tzinfo=timezone.utc)
|
||||
if expires_at.tzinfo is None:
|
||||
expires_at = expires_at.replace(tzinfo=timezone.utc)
|
||||
|
||||
idle_seconds = (now - last_activity_at).total_seconds()
|
||||
if idle_seconds > IDLE_TIMEOUT_SECONDS or expires_at < now:
|
||||
revoke_auth_session(token)
|
||||
return None
|
||||
|
||||
new_expires_at = now + timedelta(seconds=IDLE_TIMEOUT_SECONDS)
|
||||
from repositories.auth_repository import touch_auth_session_if_needed
|
||||
touch_auth_session_if_needed(
|
||||
session_token=token,
|
||||
expires_at=new_expires_at,
|
||||
throttle_seconds=SESSION_TOUCH_THROTTLE_SECONDS,
|
||||
)
|
||||
|
||||
return {
|
||||
"session_id": session_id,
|
||||
"user_id": user_id,
|
||||
"username": username,
|
||||
"created_at": created_at,
|
||||
"last_activity_at": last_activity_at,
|
||||
"expires_at": expires_at,
|
||||
"ip_address": ip_address,
|
||||
"user_agent": user_agent,
|
||||
"role": role,
|
||||
}
|
||||
211
services/game_service.py
Normal file
211
services/game_service.py
Normal file
@@ -0,0 +1,211 @@
|
||||
from repositories.match_repository import (
|
||||
get_match_by_external_id,
|
||||
mark_match_parsed,
|
||||
mark_match_parse_error,
|
||||
clear_match_squad_data,
|
||||
)
|
||||
from repositories.player_repository import (
|
||||
get_player_id_by_name_and_team,
|
||||
get_player_id_by_external_id,
|
||||
)
|
||||
from repositories.coach_repository import (
|
||||
get_coach_id_by_name_and_team,
|
||||
get_coach_id_by_external_id,
|
||||
)
|
||||
from repositories.referee_repository import get_referee_id_by_name, upsert_referee
|
||||
from repositories.match_lineup_repository import replace_match_lineups, save_match_lineup_for_editor
|
||||
from repositories.match_coach_repository import replace_match_coaches
|
||||
from repositories.match_referee_repository import replace_match_referees
|
||||
|
||||
|
||||
def sync_match_page(
|
||||
match_external_id: str,
|
||||
home_starting: list[dict],
|
||||
away_starting: list[dict],
|
||||
home_bench: list[dict],
|
||||
away_bench: list[dict],
|
||||
home_coaches: list[dict],
|
||||
away_coaches: list[dict],
|
||||
referees: list[dict],
|
||||
) -> None:
|
||||
match_row = get_match_by_external_id(match_external_id)
|
||||
if not match_row:
|
||||
raise ValueError(f"Match not found by external_id: {match_external_id}")
|
||||
|
||||
match_id, _, home_team_id, away_team_id = match_row
|
||||
clear_match_squad_data(match_id)
|
||||
|
||||
lineup_rows = []
|
||||
for player in home_starting:
|
||||
player_id = None
|
||||
|
||||
if player.get("player_external_id"):
|
||||
player_id = get_player_id_by_external_id(player["player_external_id"])
|
||||
|
||||
if player_id is None:
|
||||
player_id = get_player_id_by_name_and_team(
|
||||
player["player_name"], home_team_id
|
||||
)
|
||||
|
||||
lineup_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": home_team_id,
|
||||
"player_id": player_id,
|
||||
"player_name": player["player_name"],
|
||||
"number": player.get("number"),
|
||||
"position": player.get("position"),
|
||||
"is_captain": bool(player.get("is_captain")),
|
||||
"lineup_type": "starting",
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
for player in away_starting:
|
||||
player_id = None
|
||||
|
||||
if player.get("player_external_id"):
|
||||
player_id = get_player_id_by_external_id(player["player_external_id"])
|
||||
|
||||
if player_id is None:
|
||||
player_id = get_player_id_by_name_and_team(
|
||||
player["player_name"], away_team_id
|
||||
)
|
||||
|
||||
lineup_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": away_team_id,
|
||||
"player_id": player_id,
|
||||
"player_name": player["player_name"],
|
||||
"number": player.get("number"),
|
||||
"position": player.get("position"),
|
||||
"is_captain": bool(player.get("is_captain")),
|
||||
"lineup_type": "starting",
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
for player in home_bench:
|
||||
player_id = None
|
||||
|
||||
if player.get("player_external_id"):
|
||||
player_id = get_player_id_by_external_id(player["player_external_id"])
|
||||
|
||||
if player_id is None:
|
||||
player_id = get_player_id_by_name_and_team(
|
||||
player["player_name"], home_team_id
|
||||
)
|
||||
|
||||
lineup_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": home_team_id,
|
||||
"player_id": player_id,
|
||||
"player_name": player["player_name"],
|
||||
"number": player.get("number"),
|
||||
"position": player.get("position"),
|
||||
"is_captain": bool(player.get("is_captain")),
|
||||
"lineup_type": "bench",
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
for player in away_bench:
|
||||
player_id = None
|
||||
|
||||
if player.get("player_external_id"):
|
||||
player_id = get_player_id_by_external_id(player["player_external_id"])
|
||||
|
||||
if player_id is None:
|
||||
player_id = get_player_id_by_name_and_team(
|
||||
player["player_name"], away_team_id
|
||||
)
|
||||
|
||||
lineup_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": away_team_id,
|
||||
"player_id": player_id,
|
||||
"player_name": player["player_name"],
|
||||
"number": player.get("number"),
|
||||
"position": player.get("position"),
|
||||
"is_captain": bool(player.get("is_captain")),
|
||||
"lineup_type": "bench",
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
coach_rows = []
|
||||
|
||||
for coach in home_coaches:
|
||||
coach_id = None
|
||||
|
||||
if coach.get("coach_external_id"):
|
||||
coach_id = get_coach_id_by_external_id(coach["coach_external_id"])
|
||||
|
||||
if coach_id is None:
|
||||
coach_id = get_coach_id_by_name_and_team(coach["coach_name"], home_team_id)
|
||||
|
||||
coach_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": home_team_id,
|
||||
"coach_id": coach_id,
|
||||
"coach_name": coach["coach_name"],
|
||||
"role": coach.get("role"),
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
for coach in away_coaches:
|
||||
coach_id = None
|
||||
|
||||
if coach.get("coach_external_id"):
|
||||
coach_id = get_coach_id_by_external_id(coach["coach_external_id"])
|
||||
|
||||
if coach_id is None:
|
||||
coach_id = get_coach_id_by_name_and_team(coach["coach_name"], away_team_id)
|
||||
|
||||
coach_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"team_id": away_team_id,
|
||||
"coach_id": coach_id,
|
||||
"coach_name": coach["coach_name"],
|
||||
"role": coach.get("role"),
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
referee_rows = []
|
||||
|
||||
for referee in referees:
|
||||
referee_name = referee["referee_name"].strip()
|
||||
referee_id = get_referee_id_by_name(referee_name)
|
||||
|
||||
if referee_id is None:
|
||||
referee_id = upsert_referee(full_name=referee_name)
|
||||
|
||||
referee_rows.append(
|
||||
{
|
||||
"match_id": match_id,
|
||||
"referee_id": referee_id,
|
||||
"referee_name": referee_name,
|
||||
"role": referee.get("role"),
|
||||
"source": "parser",
|
||||
}
|
||||
)
|
||||
|
||||
try:
|
||||
replace_match_lineups(match_id, lineup_rows)
|
||||
for row in coach_rows:
|
||||
row["side"] = "home" if row["team_id"] == home_team_id else "away"
|
||||
replace_match_coaches(match_id, coach_rows)
|
||||
|
||||
|
||||
replace_match_referees(match_id, referee_rows)
|
||||
mark_match_parsed(match_external_id)
|
||||
except Exception as e:
|
||||
mark_match_parse_error(match_external_id, str(e))
|
||||
raise
|
||||
46
services/players_service.py
Normal file
46
services/players_service.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from repositories.player_repository import upsert_player
|
||||
from repositories.coach_repository import upsert_coach
|
||||
|
||||
|
||||
def to_int(value, default=0) -> int:
|
||||
if value is None:
|
||||
return default
|
||||
value = str(value).strip()
|
||||
return int(value) if value.isdigit() else default
|
||||
|
||||
|
||||
def sync_team_roster(team_external_id: str, team_data: dict) -> None:
|
||||
players = team_data.get("players") or []
|
||||
coaches = team_data.get("coaches") or []
|
||||
|
||||
for player in players:
|
||||
upsert_player(
|
||||
external_id=player.get("player_id", ""),
|
||||
team_external_id=team_external_id,
|
||||
player=player.get("player", ""),
|
||||
lastname=player.get("lastname", ""),
|
||||
name=player.get("name", ""),
|
||||
number=player.get("number", ""),
|
||||
pos=player.get("pos", ""),
|
||||
amplua=player.get("amplua", ""),
|
||||
born=player.get("born", ""),
|
||||
games=to_int(player.get("games")),
|
||||
goals=to_int(player.get("goals")),
|
||||
penaltys=to_int(player.get("penaltys")),
|
||||
assists=to_int(player.get("assists")),
|
||||
yellows=to_int(player.get("yellows")),
|
||||
reds=to_int(player.get("reds")),
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
for coach in coaches:
|
||||
upsert_coach(
|
||||
external_id=coach.get("coach_id", ""),
|
||||
team_external_id=team_external_id,
|
||||
player=coach.get("player", ""),
|
||||
lastname=coach.get("lastname", ""),
|
||||
name=coach.get("name", ""),
|
||||
born=coach.get("born", ""),
|
||||
amplua=coach.get("amplua", ""),
|
||||
is_active=True,
|
||||
)
|
||||
19
services/schedule_service.py
Normal file
19
services/schedule_service.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from repositories.match_repository import upsert_match_by_team_external_ids
|
||||
|
||||
|
||||
def sync_matches(matches_data: list[dict]) -> None:
|
||||
for match in matches_data:
|
||||
upsert_match_by_team_external_ids(
|
||||
external_id=match["external_id"],
|
||||
home_team_external_id=match["home_team_external_id"],
|
||||
away_team_external_id=match["away_team_external_id"],
|
||||
match_date=match.get("match_date"),
|
||||
status=match.get("status", "scheduled"),
|
||||
home_score=match.get("home_score"),
|
||||
away_score=match.get("away_score"),
|
||||
tour=match.get("tour"),
|
||||
season=match.get("season"),
|
||||
place=match.get("place"),
|
||||
date_raw=match.get("date_raw"),
|
||||
score_add=match.get("score_add"),
|
||||
)
|
||||
8
services/standings_service.py
Normal file
8
services/standings_service.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from repositories.standings_repository import replace_standings_for_season
|
||||
|
||||
|
||||
def sync_standings(season: str, standings_rows: list[dict]) -> None:
|
||||
replace_standings_for_season(
|
||||
season=season,
|
||||
standings_rows=standings_rows,
|
||||
)
|
||||
14
services/teams_service.py
Normal file
14
services/teams_service.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from repositories.team_repository import upsert_team
|
||||
|
||||
|
||||
def sync_teams(teams_data: list[dict]) -> None:
|
||||
for team in teams_data:
|
||||
upsert_team(
|
||||
external_id=team["external_id"],
|
||||
name=team["name"],
|
||||
logo_url=team["logo_url"],
|
||||
games=int(team.get("games", 0) or 0),
|
||||
wins=int(team.get("wins", 0) or 0),
|
||||
goals=int(team.get("goals", 0) or 0),
|
||||
tournaments=int(team.get("tournaments", 0) or 0),
|
||||
)
|
||||
424
services/vmix_json_service.py
Normal file
424
services/vmix_json_service.py
Normal file
@@ -0,0 +1,424 @@
|
||||
# services/vmix_json_service.py
|
||||
from db import get_connection
|
||||
from repositories.match_lineup_repository import get_match_lineup_for_vmix
|
||||
|
||||
|
||||
def build_lineup_json(match_id, home_team_id, away_team_id, name, team_a_name, team_b_name):
|
||||
lineups = get_match_lineup_for_vmix(
|
||||
match_id=match_id,
|
||||
home_team_id=home_team_id,
|
||||
away_team_id=away_team_id,
|
||||
)
|
||||
|
||||
players = lineups.get(name, [])
|
||||
|
||||
result = []
|
||||
|
||||
for p in players:
|
||||
suffix = []
|
||||
if "вратарь" in (p.get("pos") or "").lower():
|
||||
suffix.append("ВР")
|
||||
|
||||
if p.get("is_captain"):
|
||||
suffix.append("К")
|
||||
number = p.get("number", "")
|
||||
lastname = p.get("last_name", "")
|
||||
suffix_str = f'({", ".join(suffix)})' if suffix else ""
|
||||
result.append(
|
||||
{
|
||||
"number": p.get("number", ""),
|
||||
"number_lastname_amp_K": f"{number} {lastname} {suffix_str}".strip(),
|
||||
"first_name": p.get("first_name", ""),
|
||||
"last_name": p.get("last_name", ""),
|
||||
"number_fullname": f"{number} {p.get('first_name', '')} {p.get('last_name', '')}".strip(),
|
||||
"full_name": (
|
||||
p.get("first_name", "") + " " + p.get("last_name", "")
|
||||
).strip(),
|
||||
"full_name_K": (
|
||||
p.get("first_name", "") + " " + p.get("last_name", "")
|
||||
).strip()
|
||||
+ (f" {', '.join(suffix)}" if suffix else ""),
|
||||
"pos": p.get("pos", ""),
|
||||
"position": p.get("position", ""),
|
||||
"photo": (
|
||||
r"D:\Графика\ФУТБОЛ\Женская Суперлига 2026\Photo"
|
||||
+ "\\"
|
||||
+ (team_a_name if "home" in name else team_b_name)
|
||||
+ "\\"
|
||||
+ (p.get("last_name", "")
|
||||
+ " "
|
||||
+ p.get("first_name", "")).strip()
|
||||
+ ".png"
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return {"players": result}
|
||||
|
||||
|
||||
def get_vmix_match_info_by_token(session_token: str):
|
||||
query = """
|
||||
SELECT
|
||||
ms.match_id,
|
||||
m.match_date,
|
||||
m.place,
|
||||
m.stadium_id,
|
||||
ht.id AS home_team_id,
|
||||
ht.name AS home_team_name,
|
||||
|
||||
COALESCE(ht.full_name, ht.name) AS home_team_full_name,
|
||||
COALESCE(ht.short_name_3, '') AS home_team_short_name,
|
||||
|
||||
at.id AS away_team_id,
|
||||
at.name AS away_team_name,
|
||||
|
||||
COALESCE(at.full_name, at.name) AS away_team_full_name,
|
||||
COALESCE(at.short_name_3, '') AS away_team_short_name,
|
||||
COALESCE(s.stadium_gfx, s.name, m.place, '') AS stadium_name,
|
||||
|
||||
m.tour,
|
||||
ht.logo_path AS home_logo,
|
||||
REPLACE(at.logo_path, 'HOME', 'AWAY') AS away_logo,
|
||||
ref1.referee_name AS referee1,
|
||||
ref2.referee_name AS referee2,
|
||||
ref3.referee_name AS referee3,
|
||||
ref4.referee_name AS referee4,
|
||||
|
||||
CASE
|
||||
WHEN ht.name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(ht.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Белый')
|
||||
WHEN ht.name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(ht.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Белый')
|
||||
ELSE REPLACE(ht.logo_path, 'HOME\\', '')
|
||||
END AS home_logo1,
|
||||
|
||||
CASE
|
||||
WHEN at.name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(at.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Белый')
|
||||
WHEN at.name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(at.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Белый')
|
||||
ELSE REPLACE(at.logo_path, 'HOME\\', '')
|
||||
END AS away_logo1,
|
||||
|
||||
CASE
|
||||
WHEN ht.name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(ht.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Синий')
|
||||
WHEN ht.name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(ht.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Синий')
|
||||
ELSE REPLACE(ht.logo_path, 'HOME\\', '')
|
||||
END AS home_logo2,
|
||||
|
||||
CASE
|
||||
WHEN at.name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(at.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Синий')
|
||||
WHEN at.name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(at.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Синий')
|
||||
ELSE REPLACE(at.logo_path, 'HOME\\', '')
|
||||
END AS away_logo2,
|
||||
|
||||
ht.city AS home_city,
|
||||
at.city AS away_city,
|
||||
|
||||
TRIM(COALESCE(c1.name, '') || ' ' || COALESCE(c1.lastname, '')) AS coach_name1,
|
||||
c1.amplua AS coach_amplua1,
|
||||
|
||||
TRIM(COALESCE(c2.name, '') || ' ' || COALESCE(c2.lastname, '')) AS coach_name2,
|
||||
c2.amplua AS coach_amplua2
|
||||
|
||||
FROM match_sessions ms
|
||||
JOIN matches m ON m.id = ms.match_id
|
||||
JOIN teams ht ON ht.id = m.home_team_id
|
||||
JOIN teams at ON at.id = m.away_team_id
|
||||
|
||||
LEFT JOIN match_referees ref1 ON ref1.match_id = m.id AND ref1.role = 'Главный судья'
|
||||
LEFT JOIN match_referees ref2 ON ref2.match_id = m.id AND ref2.role = 'Ассистент судьи №1'
|
||||
LEFT JOIN match_referees ref3 ON ref3.match_id = m.id AND ref3.role = 'Ассистент судьи №2'
|
||||
LEFT JOIN match_referees ref4 ON ref4.match_id = m.id AND ref4.role = 'Резервный судья'
|
||||
|
||||
LEFT JOIN match_coaches mc1 ON mc1.match_id = m.id AND mc1.side = 'home'
|
||||
LEFT JOIN coaches c1 ON c1.id = mc1.coach_id
|
||||
|
||||
LEFT JOIN match_coaches mc2 ON mc2.match_id = m.id AND mc2.side = 'away'
|
||||
LEFT JOIN coaches c2 ON c2.id = mc2.coach_id
|
||||
|
||||
LEFT JOIN stadiums s ON s.id = m.stadium_id
|
||||
|
||||
WHERE ms.session_token = %s
|
||||
LIMIT 1;
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (session_token,))
|
||||
return cur.fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_vmix_standings(session_token: str):
|
||||
query = """
|
||||
SELECT
|
||||
s.position,
|
||||
t.full_name,
|
||||
CASE
|
||||
WHEN t.full_name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(t.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Белый')
|
||||
WHEN t.full_name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(t.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Белый')
|
||||
ELSE REPLACE(t.logo_path, 'HOME\\', '')
|
||||
END AS logo,
|
||||
s.played,
|
||||
s.wins,
|
||||
s.losses,
|
||||
s.draws,
|
||||
s.points_for || ' - ' || s.points_against AS score,
|
||||
s.points,
|
||||
s.team_id
|
||||
FROM standings s
|
||||
LEFT JOIN teams t ON s.team_id = t.id
|
||||
ORDER BY s.position
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (session_token,))
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_vmix_schedule(session_token: str):
|
||||
query = """
|
||||
SELECT
|
||||
CASE
|
||||
WHEN t1.full_name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(t1.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Белый')
|
||||
WHEN t1.full_name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(t1.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Белый')
|
||||
ELSE REPLACE(t1.logo_path, 'HOME\\', '')
|
||||
END AS logo1,
|
||||
CASE
|
||||
WHEN t2.full_name ILIKE '%%динамо%%'
|
||||
THEN REPLACE(REPLACE(t2.logo_path, 'HOME\\', ''), 'Динамо', 'Динамо_Белый')
|
||||
WHEN t2.full_name ILIKE '%%зенит%%'
|
||||
THEN REPLACE(REPLACE(t2.logo_path, 'HOME\\', ''), 'Зенит', 'Зенит_Белый')
|
||||
ELSE REPLACE(t2.logo_path, 'HOME\\', '')
|
||||
END AS logo2,
|
||||
m.home_score,
|
||||
m.away_score,
|
||||
m.match_date,
|
||||
m.status,
|
||||
m.id
|
||||
FROM matches m
|
||||
LEFT JOIN teams t1 ON m.home_team_id = t1.id
|
||||
LEFT JOIN teams t2 ON m.away_team_id = t2.id
|
||||
WHERE m.tour = %s
|
||||
ORDER BY m.match_date, m.id
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (session_token[10],))
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_vmix_team_formations(session_token: str, team_id: int):
|
||||
query = """
|
||||
SELECT
|
||||
p.last_name,
|
||||
mf.is_captain,
|
||||
p.number,
|
||||
p.position,
|
||||
p.first_name
|
||||
FROM match_formations mf
|
||||
LEFT JOIN players p ON p.id = mf.player_id
|
||||
WHERE mf.match_id = %s and mf.team_id = %s
|
||||
ORDER BY mf.id
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (session_token[1], team_id))
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_vmix_scoreboard_info(session_token: str):
|
||||
query = """
|
||||
WITH yellow_reds AS (
|
||||
SELECT
|
||||
side,
|
||||
player_id
|
||||
FROM match_events_ui
|
||||
WHERE match_id = %s
|
||||
AND type = 'yellow'
|
||||
AND player_id IS NOT NULL
|
||||
GROUP BY side, player_id
|
||||
HAVING COUNT(*) >= 2
|
||||
),
|
||||
direct_reds AS (
|
||||
SELECT
|
||||
side,
|
||||
COUNT(*) AS cnt
|
||||
FROM match_events_ui
|
||||
WHERE match_id = %s
|
||||
AND type = 'red'
|
||||
GROUP BY side
|
||||
),
|
||||
two_yellow_reds AS (
|
||||
SELECT
|
||||
side,
|
||||
COUNT(*) AS cnt
|
||||
FROM yellow_reds
|
||||
GROUP BY side
|
||||
),
|
||||
red_totals AS (
|
||||
SELECT
|
||||
s.side,
|
||||
COALESCE(dr.cnt, 0) + COALESCE(tyr.cnt, 0) AS red_count
|
||||
FROM (
|
||||
SELECT 'home' AS side
|
||||
UNION ALL
|
||||
SELECT 'away' AS side
|
||||
) s
|
||||
LEFT JOIN direct_reds dr ON dr.side = s.side
|
||||
LEFT JOIN two_yellow_reds tyr ON tyr.side = s.side
|
||||
)
|
||||
SELECT
|
||||
-- голы
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
WHEN m.side = 'home' AND m.type IN ('goal', 'penalty', 'own_goal')
|
||||
THEN 1 ELSE 0
|
||||
END
|
||||
), 0) AS home_goals,
|
||||
|
||||
COALESCE(SUM(
|
||||
CASE
|
||||
WHEN m.side = 'away' AND m.type IN ('goal', 'penalty', 'own_goal')
|
||||
THEN 1 ELSE 0
|
||||
END
|
||||
), 0) AS away_goals,
|
||||
|
||||
-- домашние карточки
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'home') >= 1 THEN '#FF0000' ELSE '#FF000000' END AS home_red_1,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'home') >= 2 THEN '#FF0000' ELSE '#FF000000' END AS home_red_2,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'home') >= 3 THEN '#FF0000' ELSE '#FF000000' END AS home_red_3,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'home') >= 4 THEN '#FF0000' ELSE '#FF000000' END AS home_red_4,
|
||||
|
||||
-- гостевые карточки
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'away') >= 1 THEN '#FF0000' ELSE '#FF000000' END AS away_red_1,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'away') >= 2 THEN '#FF0000' ELSE '#FF000000' END AS away_red_2,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'away') >= 3 THEN '#FF0000' ELSE '#FF000000' END AS away_red_3,
|
||||
CASE WHEN (SELECT red_count FROM red_totals WHERE side = 'away') >= 4 THEN '#FF0000' ELSE '#FF000000' END AS away_red_4
|
||||
|
||||
FROM match_events_ui m
|
||||
WHERE m.match_id = %s;
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (session_token[1],session_token[1],session_token[1]))
|
||||
return cur.fetchone()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def get_vmix_players_goal(match_id: int):
|
||||
query = """
|
||||
WITH goal_events AS (
|
||||
SELECT
|
||||
meu.side,
|
||||
COALESCE(p.last_name, meu.player_name) AS last_name,
|
||||
meu.seconds,
|
||||
meu.id,
|
||||
meu.type,
|
||||
CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM match_events_ui m2
|
||||
WHERE m2.match_id = meu.match_id
|
||||
AND m2.type = 'period_start_2h'
|
||||
AND m2.seconds <= meu.seconds
|
||||
) THEN 2
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM match_events_ui m2
|
||||
WHERE m2.match_id = meu.match_id
|
||||
AND m2.type = 'period_start_1h'
|
||||
AND m2.seconds <= meu.seconds
|
||||
) THEN 1
|
||||
ELSE NULL
|
||||
END AS period_no
|
||||
FROM match_events_ui meu
|
||||
LEFT JOIN players p ON p.id = meu.player_id
|
||||
WHERE meu.match_id = %s
|
||||
AND meu.type IN ('goal', 'penalty', 'own_goal')
|
||||
),
|
||||
grouped_players AS (
|
||||
SELECT
|
||||
side,
|
||||
last_name,
|
||||
MIN(seconds) AS first_goal_seconds,
|
||||
STRING_AGG(
|
||||
(
|
||||
CASE
|
||||
WHEN period_no = 1 AND seconds > 2700
|
||||
THEN '45''' || '+ ' || ((seconds - 2700) / 60)::int::text
|
||||
|
||||
WHEN period_no = 2 AND seconds > 5400
|
||||
THEN '90''' || '+ ' || ((seconds - 5400) / 60)::int::text
|
||||
|
||||
WHEN period_no = 2
|
||||
THEN (46 + ((seconds - 2700) / 60)::int)::text || ''''
|
||||
|
||||
ELSE (1 + (seconds / 60)::int)::text || ''''
|
||||
END
|
||||
) ||
|
||||
CASE
|
||||
WHEN type = 'own_goal' THEN ' (АГ)'
|
||||
WHEN type = 'penalty' THEN ' (П)'
|
||||
ELSE ''
|
||||
END,
|
||||
', ' ORDER BY seconds, id
|
||||
) AS goal_minutes
|
||||
FROM goal_events
|
||||
GROUP BY side, last_name
|
||||
),
|
||||
home_rows AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY first_goal_seconds, last_name) AS rn,
|
||||
last_name || ' ' || goal_minutes AS player_name1
|
||||
FROM grouped_players
|
||||
WHERE side = 'home'
|
||||
),
|
||||
away_rows AS (
|
||||
SELECT
|
||||
ROW_NUMBER() OVER (ORDER BY first_goal_seconds, last_name) AS rn,
|
||||
last_name || ' ' || goal_minutes AS player_name2
|
||||
FROM grouped_players
|
||||
WHERE side = 'away'
|
||||
)
|
||||
SELECT
|
||||
COALESCE(h.player_name1, '') AS player_name1,
|
||||
COALESCE(a.player_name2, '') AS player_name2
|
||||
FROM home_rows h
|
||||
FULL OUTER JOIN away_rows a ON a.rn = h.rn
|
||||
ORDER BY COALESCE(h.rn, a.rn);
|
||||
"""
|
||||
|
||||
conn = get_connection()
|
||||
try:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(query, (match_id,))
|
||||
return cur.fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
Reference in New Issue
Block a user